socket编程花了我三四天的事件终于将这个程序给实现了
所谓的多人聊天室,其实不过是客户端创建一个数据接收线程和数据发送线程,而在服务器端创建一个套接字数组,开启一个接受连接请求线程,不断接受来自客户端的连接请求,然后将建立的连接所形成的新套接字描述符存进套接字数组,并针对所存储的套接字描述符建立多个数据接收线程,对于所接收到的数据,开启一个数据转发进程,对套接字数组中的每个客户端将收到的数据进行转发。服务器就是起到这个一个数据转发的功能。
附上代码
客户端:
#include <stdio.h> #include <winsock2.h> #include <pthread.h> #pragma comment(lib,"ws2_32.lib") char buffer[4096] = {0}; int iRecvLen = 0; int iSnedLen = 0; char name[20]; void THRE_RECV(SOCKET ClientSocket) { char buffer[50]={0}; while(1) { memset(buffer, 0, sizeof(buffer));///接收消息 iRecvLen = recv(ClientSocket, buffer, sizeof(buffer), 0); if (SOCKET_ERROR == iRecvLen) { printf("send failed with error code: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return -1; } ///printf("recv %d bytes from %s: ", iRecvLen, nameOther);//为了美观最好不要打印这个了 //strcat(buffer, "\0"); buffer[iRecvLen] = 0; printf("\n%s\n", buffer); } } int main() { WSADATA wsaData = { 0 };///存放套接字信息,WSADATA结构被用来保存AfxSocketInit函数返回的WindowsSockets初始化信息。 SOCKET ClientSocket = INVALID_SOCKET;///客户端套接字 ///printf("%d\n",INVALID_SOCKET); SOCKADDR_IN ServerAddr = { 0 };///服务端地址 USHORT uPort = 18000;///服务端端口 ///初始化套接字 if(WSAStartup(MAKEWORD(2,2), &wsaData))///该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本 ///第二个参数返回请求的socket版本信息 { printf("WSAStartup failed with error code: %d\n", WSAGetLastError()); return -1; } ///判断套接字版本 if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { printf("wVersion was not 2.2\n"); return -1; } ///创建套接字 ClientSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);///AF_INET代表一个地址族,SOCK_STREAM表示为TCP协议的流服务,IPPROTO_TCP的值为6 ///printf("%d\n",IPPROTO_TCP); if (ClientSocket == INVALID_SOCKET) { printf("socket初始化失败并返回错误代码: %d\n", WSAGetLastError()); return -1; } ///输入服务器IP printf("Please input the server's IP:"); char IP[32] = { 0 }; gets(IP); ///输入聊天的用户名 printf("Please input the Client's username:"); char name[32] = {0}; memset(name,0,sizeof(name)); gets(name); ///设置服务器地址,填充服务器地址的结构 ServerAddr.sin_family = AF_INET; ServerAddr.sin_port = htons(uPort);///服务器端口 ServerAddr.sin_addr.S_un.S_addr = inet_addr(IP);///服务器地址 printf("Connecting...........\n"); ///连接服务器 if(SOCKET_ERROR == connect(ClientSocket, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr))) { printf("Connect failed with error code: %d \n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return -1; } printf("Successfuuly got a connection from IP:%s Port:%d\n\n\n\n",inet_ntoa(ServerAddr.sin_addr),htons(ServerAddr.sin_port)); _beginthreadex(NULL,0,&THRE_RECV,ClientSocket,NULL,0); iSnedLen = send(ClientSocket,name,strlen(name),0); if(SOCKET_ERROR == iSnedLen) { printf("send failed with error code: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return -1; } while(1) { memset(buffer, 0, sizeof(buffer)); ///发送消息 gets(buffer); if(strcmp(buffer,"bye") == 0) break; printf("%s: ", name); iSnedLen = send(ClientSocket, buffer, strlen(buffer), 0); if (SOCKET_ERROR == iSnedLen) { printf("send failed with error code: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return -1; } } closesocket(ClientSocket); WSACleanup(); system("pause"); return 0; }
服务器端:
#include <WinSock2.h> #include <process.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #pragma comment(lib,"ws2_32.lib") #define SEND_OVER 1 ///已经转发消息 #define SEND_YET 0 ///还没转发消息 typedef struct _Client { SOCKET sClient; ///客户端套接字 char buf[128]; ///数据缓冲区 char userName[16]; ///客户端用户名 char IP[20]; ///客户端IP UINT_PTR flag; ///标记客户端,用来区分不同的客户端 int first; }Client; Client g_Client[10] = { 0 }; int SENDFFFF = 0; int Rflag = 0; SOCKADDR_IN ServerAddr = { 0 };///服务端地址 SOCKADDR_IN ClientAddr = { 0 };///客户端地址 int iClientAddrLen = sizeof(ClientAddr); SOCKET g_ServerSocket = INVALID_SOCKET; ///服务端套接字 SOCKADDR_IN g_ClientAddr = { 0 }; ///客户端地址 int g_iClientAddrLen = sizeof(g_ClientAddr); USHORT uPort = 18000; ///服务器监听端口 unsigned __stdcall ThreadSend(void* param) { while(1) { if(SENDFFFF==0) continue; else { SOCKET client = INVALID_SOCKET; ///创建一个临时套接字来存放要转发的客户端套接字 char temp[128]; memset(temp,0,sizeof(temp)); ///创建一个临时的数据缓冲区,用来存放接收到的数据 memcpy(temp, g_Client[Rflag].buf, sizeof(temp)); printf("temp = %s\n",temp); int k = 1; while(k<=5) { if(k!=Rflag) { sprintf(g_Client[k].buf, "%s: %s", g_Client[Rflag].userName, temp);///添加一个用户名头 } k++; } k=1; while(k<=5) { if(strlen(temp) != 0&&g_Client[k].sClient!=INVALID_SOCKET&&k!=Rflag) { int ret = send(g_Client[k].sClient, g_Client[k].buf, strlen(g_Client[k].buf), 0); printf("g_Client[k].buf == .............%s\n",g_Client[k].buf); if(ret==SOCKET_ERROR) return 1; } k++; } Rflag = 0; SENDFFFF = 0; } } return 0; } unsigned __stdcall THREAD_RECV(SOCKET ClientSocket) { int flag = 0; int u = 1; while(u<=5) { if(g_Client[u].sClient==ClientSocket&&g_Client!=INVALID_SOCKET) { flag = u; } u++; } printf("flag = %d\n",flag); char temp[80] = {0}; while(1) { memset(temp, 0, sizeof(temp)); int ret = recv(ClientSocket, temp, sizeof(temp), 0); ///接收数据 printf("%s\n",temp); if (ret == SOCKET_ERROR) { printf("ClientSocket=%d error=%d",ClientSocket,errno); //getchar(); continue; } //iStatus = SEND_YET; ///设置转发状态为未转发 //flag = client == g_Client[0].sClient ? 1 : 0; ///这个要设置,否则会出现自己给自己发消息的BUG int k = 1; while(k<=5) { if(k!=flag&&g_Client[flag].first==1) memcpy(g_Client[k].buf, temp, strlen(g_Client[k].buf)); printf("%s\n",g_Client[k].buf); k++; } if(g_Client[flag].first==0)///如果这是第一次传过来的数,说明这是一个用户名,不需要进行转发,将其复制到该相应的用户名当中 { memcpy(g_Client[flag].userName,temp,sizeof(temp)); g_Client[flag].first = 1; } else { Rflag = flag; SENDFFFF = 1; ///开启一个转发线程,flag标记着要转发给哪个客户端 } } } unsigned __stdcall THREAD_ACCEPT(SOCKET ClientSocket) { int num = 0; while(1) { num = 0; int k = 1; while(k<=5) { if(g_Client[k].sClient!=INVALID_SOCKET) { num++; k++; continue; } else { g_Client[k].sClient = accept(ClientSocket, (SOCKADDR*)&g_ClientAddr, &g_iClientAddrLen); if(g_Client[k].sClient == INVALID_SOCKET) { printf("accept failed with error code: %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); return -1; } //int len = recv(g_Client[k].sClient, g_Client[k].userName, 15, 0); ///接收用户名 //g_Client[k].userName[len]=0; //printf("\nlen = %d\n",len); printf("Successfuuly got a connection from IP:%s ,Port: %d,UerName: %s\n",inet_ntoa(g_ClientAddr.sin_addr), htons(g_ClientAddr.sin_port), g_Client[k].userName); //printf("%s\n",g_Client[k].sClient); printf("k = %d\n",k); memcpy(g_Client[k].IP, inet_ntoa(g_ClientAddr.sin_addr), sizeof(g_Client[k].IP)); ///记录客户端IP g_Client[k].flag = g_Client[k].sClient; ///不同的socke有不同UINT_PTR类型的数字来标识 num++; k++; break; } } //printf("num = %d\n",num); if(num>=3) { int l =1; while(l<=5) { if(g_Client[l].sClient!=INVALID_SOCKET) { _beginthreadex(NULL,0,&THREAD_RECV,g_Client[l].sClient,0,0); } l++; } } } } int main() { ///存放套接字信息的结构 WSADATA wsaData = { 0 }; SOCKET ServerSocket = INVALID_SOCKET;///服务端套接字 SOCKET ClientSocket = INVALID_SOCKET;///客户端套接字 int pp = 1; while(pp<=6) { memset(g_Client[pp].buf,0,sizeof(g_Client[pp].buf)); g_Client[pp].first = 0; memset(g_Client[pp].IP,0,sizeof(g_Client[pp].IP)); g_Client[pp].sClient = INVALID_SOCKET; memset(g_Client[pp].userName,0,sizeof(g_Client[pp].userName)); pp++; } if (WSAStartup(MAKEWORD(2, 2), &wsaData)) { printf("WSAStartup failed with error code: %d\n", WSAGetLastError()); return -1; } ///判断版本 if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { printf("wVersion was not 2.2\n"); return -1; } ///创建套接字 ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ServerSocket == INVALID_SOCKET) { printf("socket failed with error code: %d\n", WSAGetLastError()); return -1; } ///设置服务器地址 ServerAddr.sin_family = AF_INET;///连接方式 ServerAddr.sin_port = htons(uPort);///服务器监听端口 ServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);///任何客户端都能连接这个服务器 ///初始化套接字 ///绑定服务器 if (SOCKET_ERROR == bind(ServerSocket, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr))) { printf("bind failed with error code: %d\n", WSAGetLastError()); closesocket(ServerSocket); return -1; } ///监听有无客户端连接 if (SOCKET_ERROR == listen(ServerSocket, 1)) { printf("listen failed with error code: %d\n", WSAGetLastError()); closesocket(ServerSocket); WSACleanup(); return -1; } _beginthreadex(NULL, 0, ThreadSend, NULL,0,NULL); _beginthreadex(NULL,0,&THREAD_ACCEPT,ServerSocket,NULL,0); int k = 0; while(k<100)///让主线程休眠,不让它关闭TCP连接. { Sleep(10000000); k++; } ///关闭套接字 int j = 1; while(j<6) { if (g_Client[j].sClient != INVALID_SOCKET) closesocket(g_Client[j].sClient); j++; } closesocket(g_ServerSocket); WSACleanup(); return 0; /* ClientSocket = accept(ServerSocket, (SOCKADDR*)&ClientAddr, &iClientAddrLen);///accept为阻塞函数 if (ClientSocket == INVALID_SOCKET) { printf("accept failed with error code: %d\n", WSAGetLastError()); closesocket(ServerSocket); WSACleanup(); return -1; } printf("Successfuuly got a connection from IP:%s Port:%d\n\n\n\n",inet_ntoa(ClientAddr.sin_addr),htons(ClientAddr.sin_port)); */ }
对于代码的思路和注释都写在代码里面了,希望这个文档对于刚学习网络编程的人有所帮助。
不过这个程序有一个问题就是在客户端断开连接的时候会出现错误,还有待解决!!可以对每个客户端的连接建立一个线程进行单独的循环查询,当检测到其断开时对其进行关闭套接字,关闭相应的线程。