简易的命令行聊天室程序(Winsock,服务器&客户端)

       代码中使用WinSock2函数库,设计并实现了简单的聊天室功能。该程序为命令行程序。对于服务器和客户端,需要:

  1. 服务器:创建监听套接字,并按本地主机绑定;主线程监听并接受来自客户端的请求,并为该客户端创建单独线程;接收与发送消息的事务放在同各客户端的单独线程中处理。
  2. 客户端:创建套接字,并对服务器发起连接;主线程始终处于发送消息的状态;副线程用于不断从服务器接收来自其他客户端的消息。

 

实验代码及部分说明如下:

  1. 服务器(Server.cpp)

 

#include <WinSock2.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include<vector>

 

using namespace std;

 

#define DEFAULT_PORT 7321

#define BUF_SIZE 1024

 

typedef struct{

      SOCKET info_socket_rev;

      sockaddr_in info_addr_rev;

} info2thread;

 

char szMessage[BUF_SIZE];

std::vector<SOCKET>  vClientSockets;  //用于保存连接中的Client套接字

 

DWORD WINAPI ThreadProc(PVOID pParam) ;

 

int main(int argc,char* argv[]){

      WSADATA   wsd;

      SOCKET    sListen,sClient;

      sockaddr_in    local,client;

      int    iAddrSize;

      HANDLE    hThread;

    DWORD    dwThreadId;

      info2thread infoParam;

 

      //加载Winsock

      if(WSAStartup(MAKEWORD(2,2),&wsd) != 0){

           printf("Failed to load Winsock!\n");

           exit(1);

      }

      //创建监听套接字及其地址信息结构体

      sListen = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);

      local.sin_addr.s_addr = htonl(INADDR_ANY);

      local.sin_port = htons(DEFAULT_PORT);

      local.sin_family = AF_INET;

      //j将套接字和地址信息绑定

      if(bind(sListen,(const sockaddr*) &local,sizeof(local)) == SOCKET_ERROR){

           printf("bind() failed:%d\n",WSAGetLastError());

           return 1;

      }

      //监听

      listen(sListen,7);

      printf("Server is Ready, listening on Port:%d.\n>\n",ntohs(local.sin_port));

     

 

      while(1){

           //处理连接请求

           iAddrSize = sizeof(client);

           sClient = accept(sListen,(sockaddr*)&client,&iAddrSize);

           if(sClient == INVALID_SOCKET){

                 printf("accept() failed:%d\n",WSAGetLastError());

                 return 1;

           }

           printf("Accepted Client: %s:%d\n",

                      inet_ntoa(client.sin_addr), ntohs(client.sin_port));

          

 

           infoParam.info_addr_rev = client;     //infoParam用于构建传给线程的结构体,主要用于IP地址和Port端口的输出

           infoParam.info_socket_rev = sClient;

           //在vector中保存连接中的客户端Socket

           vClientSockets.push_back(sClient);

           //创建进程接收数据

           hThread = CreateThread(NULL,0,ThreadProc,(LPVOID)&infoParam,0,&dwThreadId);

           if (hThread == NULL){

            printf("CreateThread() failed: %d\n", GetLastError());

            break;

        }

           CloseHandle(hThread); //不再需要这个句柄,关掉它,但并非是关掉对应线程

      }

      //关闭

      closesocket(sListen);

 

      WSACleanup();

      return 0;

}

 

DWORD WINAPI ThreadProc (PVOID pParam) {

      int ret,errCode;

      info2thread *pInfo = (info2thread*) pParam;

      SOCKET sClient = pInfo->info_socket_rev,

                      sOther;

      sockaddr_in client = pInfo->info_addr_rev;

      char     szIPPort[BUF_SIZE],

                 szPort[7];

 

      //构造客户端 "IP:Port" 标识

      _itoa(ntohs(client.sin_port),szPort,10);  //itoa函数用于将整型数转化成对应的字符串,10表示十进制,末尾自动添'\0'

      strcpy(szIPPort,inet_ntoa(client.sin_addr));  //拷贝IP

      strcpy(szIPPort+strlen(inet_ntoa(client.sin_addr)),":");

      strcpy(szIPPort+strlen(inet_ntoa(client.sin_addr))+1,szPort);  //拷贝Port

 

      while(1){

           //接收来自客户端的数据

           ret = recv(sClient,szMessage,BUF_SIZE,0);

           if (ret == SOCKET_ERROR){

                 errCode = WSAGetLastError();

                 switch(errCode){

                      case 10054: //10054:客户端主动退出时的错误代码

                            printf("%s:%d has exited.\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));

                            break;

                      default:

                            printf("recv() failed:%d from %s\n", errCode,szIPPort);

                 }

 

                 break;  //跳出while(1)循环

           }

           szMessage[ret] = '\0';

           printf("Server%d REV %s: %s\n",     GetCurrentProcessId(),

                                                                              szIPPort,

                                                                              szMessage);

          

           //向正在连接的所有其他客户端发送消息(聊天室)

           for(vector<SOCKET>::iterator it = vClientSockets.begin();it != vClientSockets.end(); ++it){

                 if(*it == sClient)      continue;

                

                 sOther = *it;

                 //广播客户标识信息

                 ret = send(sOther,szIPPort,strlen(szIPPort),0);

                 if (ret == SOCKET_ERROR); //do nothing

                 //广播客户消息

                 ret = send(sOther,szMessage,strlen(szMessage),0);

                 if (ret == SOCKET_ERROR); //do nothing

           }

      }

 

      //关闭套接字,从vector中删除对应的元素

      for(vector<SOCKET>::iterator it = vClientSockets.begin(); it != vClientSockets.end();  ){

           if(*it == sClient){

                 it = vClientSockets.erase(it);

                 break;

           }

           else{

                 ++it;

           }

      }

      closesocket(sClient);

 

      return 0;

}

 

 

 

 

  1. 客户端(Client.cpp)

 

#include <WinSock2.h>

#include <stdio.h>

 

#define DEFAULT_PORT        7321

#define BUF_SIZE 128

 

char szMessage[BUF_SIZE],

           szRecieved[BUF_SIZE];

 

DWORD WINAPI ThreadProc (PVOID);

 

int main(int argc, char *argv[]){

      WSADATA wsd;

      SOCKET sClient;

      struct sockaddr_in server;

      HANDLE    hThread;

    DWORD    dwThreadId;

      int ret;

 

 

      // 加载Winsock

      if(WSAStartup(MAKEWORD(2,2), &wsd) != 0){

           printf("Failed to load Winsock library!\n");

        return 1;

      }

      //创建套接字

      sClient = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);

      if(sClient == INVALID_SOCKET){

           printf("socket() failed on %d\n",WSAGetLastError());

           return 1;

      }

      //填写服务器地址信息结构体

      server.sin_addr.s_addr = inet_addr("127.0.0.1");

      server.sin_port = htons(DEFAULT_PORT);

      server.sin_family = AF_INET;

      //查询服务器

 

      //连接

      if(connect(  sClient,(const sockaddr*)&server,

                            sizeof(server)) == SOCKET_ERROR){

           printf("connect() failed: %d\n", WSAGetLastError());   

        return 1;

      }

      else

      {

           printf("This Client is Connected.\n>\n");

      }

 

      //创建一个用于不停接收服务器数据的进程

      hThread = CreateThread(NULL,0,ThreadProc,(LPVOID)&sClient,0,&dwThreadId);

      if (hThread == NULL){

            printf("CreateThread() failed: %d, can't receive message from SERVER!\n", GetLastError());

      }

      CloseHandle(hThread); //不再需要这个句柄,关掉它,但并非是关掉对应线程

 

      //发送数据

      do{

           gets_s(szMessage,BUF_SIZE-1);

           ret = send(sClient,szMessage,strlen(szMessage),0);

           if (ret == SOCKET_ERROR){

            printf("send() failed: %d\n", WSAGetLastError());

            break;

        }

      }while(ret);

 

      //关闭套接字

      closesocket(sClient);

      WSACleanup();

 

      return 0;

}

 

DWORD WINAPI ThreadProc (PVOID pParam){

      SOCKET sRecv = *(SOCKET*)pParam;

      int ret;

 

      while(1){

 

           //接收标识

           ret = recv(sRecv,szRecieved,BUF_SIZE,0);

           if(ret == SOCKET_ERROR){

                 printf("[Unknown]"); //标识接收失败,则统一认其为Unknown用户

           }

           else{

                 szRecieved[ret] = '\0';

                 printf("[%s]",szRecieved);

           }

 

           //分割符

           printf(":");

 

           //接收消息

           ret = recv(sRecv,szRecieved,BUF_SIZE,0);

           if(ret == SOCKET_ERROR){

                 printf("recv() from server failed: %d\n",WSAGetLastError());

                 continue;

           }

           szRecieved[ret] = '\0';

           printf("%s\n",szRecieved);

      }

 

      return 0;

}

      

       运行结果截图:

      

posted @ 2013-12-10 15:44  l3sl!e  阅读(2008)  评论(0编辑  收藏  举报