`

Socket编程实现多人聊天室(C语言版)

 
阅读更多

       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));

*/



}

对于代码的思路和注释都写在代码里面了,希望这个文档对于刚学习网络编程的人有所帮助。

 不过这个程序有一个问题就是在客户端断开连接的时候会出现错误,还有待解决!!可以对每个客户端的连接建立一个线程进行单独的循环查询,当检测到其断开时对其进行关闭套接字,关闭相应的线程。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics