了解基础在我的另一篇博客网络通信入门 简单的服务器客户端的链接
这是我画的大概的流程图:
这是服务器的代码,(客户端就是发消息和上边链接中的客户端代码原理一样)剩下的在代码后边解释:
#include <WinSock2.h> //Socketk编程所需要的头文件
#include <windows.h>
#include <iostream>
#include <tchar.h>
#pragma comment(lib,"Ws2_32.lib") //需要加载的链接库
using namespace std;
//初始化Socket类库
BOOL InitializeSocket();
int _tmain(int argc, TCHAR* argv[],TCHAR *envp[])
{
setlocale(LC_ALL,"Chinese-simplified"); //Unicode下 或者 vs 下中文的显示需要
if (InitializeSocket()==FALSE) //初始化成功的话继续进行
{
return 0;
}
//创建监听套节字对象
SOCKET ListenSocket;
ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //AF_INET表示 Internet的 IP地址家族
//后两个参数就是使用TCP协议的固定形式
//TCP协议安全但是慢 UDP协议快但是不太安全
if (ListenSocket == INVALID_SOCKET) //如果是无效的Socket
{
return 0;
}
//初始本地网卡
sockaddr_in ServerAddress; //internet下服务器的套接字地址形式
/*
typedef struct sockaddr_in
{
short sin_family; //地址协议家族
USHORT sin_port; //端口号
IN_ADDR sin_addr; //如果 Internet 地址等于 INADDR_ANY,系统会自动使用当前主机配置的所有 IP 地 址,简化了程序设计
CHAR sin_zero[8]; //预留的参数
} SOCKADDR_IN, *PSOCKADDR_IN;
typedef struct in_addr {
union {
struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { USHORT s_w1,s_w2; } S_un_w;
ULONG S_addr;
} S_un;
} IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;*/
ServerAddress.sin_family = AF_INET; //地址协议家族 这个是internet
ServerAddress.sin_port = htons(8888); //htons函数是将CPU的字节顺序转化为网络字节顺序
ServerAddress.sin_addr.S_un.S_addr = INADDR_ANY; //解释在上边的结构体成员后边
//绑定套节字到本地机器
if (bind(ListenSocket, (sockaddr*)&ServerAddress, sizeof(ServerAddress)) == SOCKET_ERROR)
{
return 0;
}
//进入监听模式
listen(ListenSocket, 5); //5,代表的是允许消息同时到达的最大数量,可以更改
//声明一个套接字集合,就是数组
fd_set v1;
FD_ZERO(&v1);
//将监听套接字放入到集合中,刚开始只加入一个监听套接字,后来会加入下边的通讯套接字
FD_SET(ListenSocket, &v1);
//死循环,不停等待新的消息的到来
while (1)
{
fd_set v2 = v1; //从旧的套接字集合拷贝一份,以进行对于select函数执行之后的两个新旧的套接字之间进行比较
int IsOk = select(0, &v2, NULL, NULL, NULL); //返回值大于零则有信号到来(无论是监听套接字的accept close信号 还是通讯套接字的read write close信号),并被设置标记
if (IsOk > 0)
{
for (int i = 0; i < (int)v1.fd_count; i++) //套接字集合中一个一个的遍历 进行比较
{
if (FD_ISSET(v1.fd_array[i], &v2)) //IS SET就是当前套接字是否被设置了标记,如果没有的话进入下一个for循环
{
if (v1.fd_array[i] == ListenSocket) //如果是的话,看是不是监听套接字,不是监听套接字就是通讯套接字,进入else
{
//不懂先往下看就懂了
//是监听套接字
//接收connect信号
if (v1.fd_count < FD_SETSIZE) //在最大的连接范围内 FD_SETSIZE为64个
{
//记录客户端信息
sockaddr_in ClientAddress;
int ClientAddressLength = sizeof(ClientAddress);
//创建新的通信套接字 接受连接,这个套接字是通讯套接字
SOCKET ClientSocket = accept(ListenSocket,(SOCKADDR*)&ClientAddress, &ClientAddressLength);
//将新的通讯套接字放入到老集合中,下一次进行FOR循环,就会被识别为通讯套接字,进入下边接受通讯内容的else中
FD_SET(ClientSocket, &v1);
printf("接收到连接%s\n", ::inet_ntoa(ClientAddress.sin_addr));
/*
char Ine_Addr(INET_ADDRSTRLEN);
PCSTR str=inet_ntop(AF_INET,&ClientAddress.sin_addr,(PSTR)Ine_Addr,sizeof(Ine_Addr));\
printf("接到连接%s\n",Ine_Addr);
*/
}
else
{
_tprintf(_T("Too Much Connections!\n"));
continue;
}
}
else
{
//这个else就是当for循环判断为通讯套接字的情况
char BufferData[0x1000];
//刚才已经accept了,现在需要接受消息的内容
int ReturnLength = recv(v1.fd_array[i], BufferData, 0x1000, 0);
if (ReturnLength > 0)
{
BufferData[ReturnLength] = '\0'; //OX1000设置的缓冲区会比较大,进行末尾置零以便于显示
printf("接收到数据:%s\n", BufferData);
}
else
{
//对方关闭套接字
closesocket(v1.fd_array[i]);
//从集合中移除
FD_CLR(v1.fd_array[i], &v1);
}
}
}
} //记住这是两个循环 外边是一个while循环 里边是一个for循环 while循环相当于时间上的读秒,
//for循环相当于检索那一秒集合中的套接字受信状态并进行处理
}
}
WSACleanup(); //关闭Socket
system("pause");
return 0;
}
BOOL InitializeSocket()
{
WSADATA v1 = { 0 };
//#define MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8))
int IsOk = WSAStartup(MAKEWORD(2, 2), &v1);
if (IsOk == 0)
{
return TRUE;
}
return FALSE;
}