来自博主:
TCP通信是我在大学的第二个年头实现的,虽然过去了小半年,但是依然感觉当初做的还不错,唯一的遗憾就是没有打破局域网的限制,不过同志们可以在宿舍或者实验室,做聊天工具娱乐一下还是可以的!
(后来做了内网穿透,完全是局域网IP问题,换个全网IP就好了)
01
TCP原理概述
首先你需要明白连接的建立需要 “三次握手协议”,这种建立连接的方法是为了防止产生错误的连接而设定,三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。TCP三次握手的过程如下:
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
其次主过程可分为:服务器监听、客户端系列操作。
服务器监听:
服务器承担着主要通信的责任,在检测网络状态良好的同时,一直等待客户端的请求连接,其次将客户端发送来的消息进行数据处理,这些数据我需要放在哪里,这些数据我又该发送给谁。
客户端系列:
客户端需要做是,通过我的 IP和端口号锁定我的服务器,三次握手协议过后,即可通信。用很官方的话来讲就是:客户端的套接字提出连接请求,向服务器端套接字提出连接请求。
02
个人制作理念
TCP与UDP通信的最大的不同点就是:我们来拿A传递消息给B、C、D例子来说,UDP的通信是A把消息传递给(BCD),但是A并不知道(BCD)是否接收到消息,就像是 “村长用广播给村民传达消息,村民是否听到?村长并不知道”,关系方程可写为:{A→(B,C,D)}。 但是TCP就不同于UDP,TCP是需要在保证 两者之间相互建立连接的基础上,在进行通信,关系方程可写为:{(A↔B),(A↔C),(A↔D)}。
如果想要把B的消息传给C、D呢?
如果想要把C的消息传给B、D呢?
如果想要把D的消息传给B、C呢?
… … …
那我们该怎么办呢?
我们可能做不出来像QQ、微信那么强大的系统,但是做个小小的聊天工具还是有能力的!有句老话说的好:一口吃个胖子还真不太现实。
再回到TCP的关系方程中,{(A↔B),(A↔C),(A↔D)},他们之间的公共点就是“A”,我们要运用公共点A,把B的消息给其他人,A的价值充当着服务器,此时关系方程在此需求上变为:
{(B↔A)} 然后 {(A↔B),(A↔C),(A↔D)}
发送一条消息主要有三点:发给谁?发什么?谁发的??
例如:B发给D消息,首先B作为发送方肯定知道三点,然后我们需要让传递者A知道这三点,其次才能让消息准确的传达给接收方。B的 IP+端口+消息(谁发的?发什么?),D的 IP+端口(发给谁?),把内容打包给A,A经过数据分析处理发送给消息要去往的地方。只有这样关系方程才可以变为: {(B↔A)} 然后 {(A↔D)}
03
代码实现
(编译环境:VS2013,先运行服务器程序,在运行客户端程序即可)
服务器代码:
#pragma warning(disable:4996)
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include "winsock2.h"
#include<cstdlib>
#include <process.h>
#include<WS2tcpip.h>
#pragma comment(lib,"ws2_32.lib")//引用库文件
using namespace std;
void sendmain();
char sendbuff[1024]; //所需要发送给客户人员的内容信息
const int maxperson=20; //最大连接服务器人数
SOCKET user[maxperson]; //客户端人员
/**
* 在一个新的线程里面接收数据
*/
void Recvmain(void * p)
{
char recvBuf[1024];
SOCKET sockConn = (SOCKET)p;
while (true)
{
memset(recvBuf, 0, sizeof(recvBuf));
// //接收数据
if (recv(sockConn, recvBuf, sizeof(recvBuf), 0) != -1)
{
printf("%s\n", recvBuf);
strcpy(sendbuff, recvBuf);
}
else
break;
//_beginthread(sendmain, 1024 * 1024, NULL);
//cout << recv(sockConn, recvBuf, sizeof(recvBuf), 0) << endl;
}
closesocket(sockConn);
}
int sizefun(char sendbuff[1024]) //sendbuff长度
{
int i = 0;
while (sendbuff[i] != 0)
{
i++;
}
return i + 3;
}
void sendmain(void * p)
{
while (true)
{
if (sendbuff[0] != 0)
{
int len = sizefun(sendbuff);
for (int i = 0; i < 20; i++)
{
if (user[i] == NULL)
{
break;
}
send(user[i], sendbuff,len, 0);
//Sleep(500);
/*if (iSend == SOCKET_ERROR)
{
printf("发送失败");
break;
}*/
}
memset(sendbuff, 0, sizeof(sendbuff));
}
}
//closesocket(sockConn);
}
int main()
{
WSADATA wsaData;
int port = 8888;//端口号
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("初始化失败");
return 0;
}
//创建用于监听的套接字,即服务端的套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(port); //1024以上的端口号
/**
* INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 一般来说,在各个系统中均定义成为0值。
*/
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
int retVal = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));
if (retVal == SOCKET_ERROR){
printf("连接失败:%d\n", WSAGetLastError());
return 0;
}
if (listen(sockSrv, 10) == SOCKET_ERROR){
printf("监听失败:%d", WSAGetLastError());
return 0;
}
_beginthread(sendmain, 1024 * 1024, NULL);
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
SOCKET sockConn;
while (1)
{
//等待客户请求到来
sockConn = accept(sockSrv, (SOCKADDR *)&addrClient, &len);
if (sockConn == SOCKET_ERROR)
{
printf("等待请求失败:%d", WSAGetLastError());
break;
}
printf("\n客户端的IP是:[%s]\n", inet_ntoa(addrClient.sin_addr));
//发送提示
char sendbuf[] = "你好,服务端已经加载完毕!";
int iSend = send(sockConn, sendbuf, sizeof(sendbuf), 0);
if (iSend == SOCKET_ERROR)
{
printf("发送失败");
break;
}
for (int i = 0; i < maxperson; i++) //将当前请求加入的客户端放入user[]数组中
{
if (user[i] == sockConn)
break;
if (user[i] == NULL)
{
user[i] = sockConn;
break;
}
}
_beginthread(Recvmain, 1024 * 1024, (void*)sockConn);
}
closesocket(sockSrv);
WSACleanup();
system("pause");
return 0;
}
客户端代码:
#pragma warning(disable:4996)
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include "winsock2.h"
#include <string>
#pragma comment(lib,"ws2_32.lib")//引用库文件
#include <process.h>
using namespace std;
#define IP "127.0.0.1" //IP地址
void Recvmain(void * p)
{
char recvBuf[100];
SOCKET sock = (SOCKET)p;
int n = 0;
/*while ((n = recvfrom(sock, s, sizeof(s), 0, (sockaddr*)&sfrom, &slen)) > 0)
{
s[n] = '\0';
cout << endl << inet_ntoa(sfrom.sin_addr) << "-" << htons(sfrom.sin_port) << ": " << s << endl;
memset(s, 0, sizeof(s));
}*/
while (true)
{
sockaddr_in sfrom = { 0 };
int slen = sizeof(sfrom);//in out
memset(recvBuf, 0, sizeof(recvBuf));
// //接收数据
if (recv(sock, recvBuf, sizeof(recvBuf), 0) != -1)
{
//cout << inet_ntoa(sfrom.sin_addr) << " : ";
printf("%s\n", recvBuf);
}
else
break;
}
closesocket(sock);
}
void sendmain()
{
cout << "请输入昵称:";
string name;
cin >> name;
fflush(stdin);
cout << "\n 等待连接..\n\n";
//加载套接字
WSADATA wsaData;
char buff[1024];
memset(buff, 0, sizeof(buff));
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("初始化Winsock失败");
return ;
}
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(8888);//端口号
addrSrv.sin_addr.S_un.S_addr = inet_addr(IP);//IP地址
//创建套接字
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKET_ERROR == sockClient){
printf("Socket() error:%d", WSAGetLastError());
return ;
}
//向服务器发出连接请求
if (connect(sockClient, (struct sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET){
printf("连接失败:%d", WSAGetLastError());
return ;
}
else
{
//HANDLE hThread = CreateThread(NULL, 0, Fun, &sockClient, 0, NULL);
//接收数据
_beginthread(Recvmain, 1024 * 1024, (void*)sockClient);
}
//发送数据
string str = "###测试人员 " + name + " 客户端已加入服务器!\n";
char buffs[1024];
buffs[str.size()] = '\0';
str.copy(buffs, str.size()+1, 0);
send(sockClient, buffs,str.size(), 0);
//不断输入,然后发送
while (true)
{
string str;
getline(cin, str);
int len = str.size();
str = name +" : "+ str;
str.copy(buffs, str.size()+1, 0);
send(sockClient, buffs, str.size(), 0);
}
//关闭套接字
closesocket(sockClient);
WSACleanup();//释放初始化Ws2_32.dll所分配的资源。
system("pause");
return ;
}
完毕。