博客动态
Hi!欢迎来到Jerry Coding!公众号垃圾分类增加学习功能,关注下面的公众号一起来帮助Jerry学习更多垃圾分类知识吧!
微信扫码

扫一扫关注我

More
关于
New
RSS

socket 多线程聊天室的实现(C语言)

2019年6月25日 Jerry 1621 2019年6月27日

       人生不止眼前的苟且,代码也不止数据的增删改查,也有有趣的网络编程。如何用C语言做一个简单的服务器和客户端,实现一个聊天室程序呢?这里就简单的写一下博主的实现。

一、程序需求

实现一个简单的服务器,包括以下功能:

  • 可以监听并且与多个客户端建立TCP链接。
  • 可以接收客户端发来的消息,并发送给所有的客户端。
  • 可查询所有的客户端链接。

实现客户端有如下功能:

  • 输入服务器端口号,与服务器端建立链接。
  • 发送消息给服务器。
  • 接收并打印服务器发来的消息。

二、实现分析

       借用百度的一张图,这个流程很好的解释了socket编程的根本:

服务器端的处理流程思路:

  1. 创建socket套接字
  2. 绑定ip与端口号 
  3. 创建监听线程,等待客户端链接
  4. 接收到一个客户端链接则创建一个线程进行消息接收处理
  5. 客户端关闭,关闭相关进程
  6. 服务器关闭

客户端的处理流程思路:

  1. 创建socket套接字
  2. 输入端口号链接服务器
  3. 创建线程处理服务器消息
  4. 接收控制台输入发送给服务器
  5. 客户端关闭

socket的基本api:

      包含在在头文件“winsock2.h”,官网说明地址: https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/

三、基本的socket API

1、创建一个socket套接字:

/*
domain:协议域、地址域或协议族。常用的协议族有:AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX)、AF_ROUTE等等
type:socket类型。常用的socket类型有:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等
protocol:指定使用的协议。常用的协议有:IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等等
返回值:成功返回一个非负整数的fd,失败则返回-1
*/
int socket(int domain, int type, int protocol);

2、绑定套接字

/*
sockfd:socket文件描述符,即socket()的返回值
addr:指向sock地址的指针
addrlen:sock地址的长度
返回值:成功返回0,失败返回-1
*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

3、监听套接字

/*
sockfd:被监听的套接字
backlog:最大等待队列数量
返回值:成功返回0,失败返回-1
*/
int listen(int sockfd, int backlog);

4、连接socket套接字

/*
sockfd:客户端的套接字
addr:服务器的地址
addrlen:服务器的地址的长度
返回值:成功返回0,失败返回-1
*/
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

5、接受socket连接请求

/*
sockfd:被监听的套接字
addr:返回客户端的地址,可为NULL
addrlen:指定客户端的地址的长度,可为NULL
返回值:成功返回已连接的新套接字描述符connfd,失败返回-1
*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

6、socket接收数据

/*
sockfd:从这个socket接收数据
buf:用来保存接收到的数据
len:指定buf的长度,表示最多接收这么多个字节的数据
flags:flags,指定对应的选项,一般置为0
返回值:成功返回接收到的数据大小,返回0表示对方不再发送数据(可以理解为关闭了连接),出错返回-1
*/
int recv(int sockfd, void *buf, int len, int flags);

7、socket发送数据

/*
sockfd:向这个socket发送数据
buf:发送buf指向的数据
len:指定buf的长度,指定要发送的数据大小
flags:flags,指定对应的选项,一般置为0
返回值:成功返回已发送的数据大小,失败则返回-1
*/
int send(int sockfd, void *buf, int len, int flags);

8、从udp socket接收数据

/*
sockfd:输入参数,从该socket接收数据
buf:输出参数,将接收的数据存放在buf上
len:输入参数,指定buf的长度
flags:输入参数,flags,指定对应的选项,一般置为0
addr:输出参数,保存该数据的发送方地址
addrlen:输入参数,指定发送方地址的长度
返回值:成功返回接收到的数据大小,失败则返回-1
*/
int recvfrom(int sockfd, void *buf, int len, int flags, struct sockaddr *addr, socklen_t *addrlen);

9、向udp socket发送数据

/*
sockfd:输入参数,向该socket发送数据
buf:输入参数,发送buf指向的数据
len:输入参数,指定buf的长度
flags:输入参数,flags,指定对应的选项,一般置为0
addr:输入参数,指定接收方的地址
addrlen:输入参数,指定接收方的地址的长度
返回值:成功返回发送的数据大小,失败则返回-1
*/
int sendto(int sockfd, void *buf, int len, int flags, struct sockaddr *addr, socklen_t addrlen);

10、关闭socket

/*
fd: 要关闭的socketID
*/
int close(int fd);

四、最终实现效果

服务器端:

客户端:

1、建立多个链接

2、发送消息

3、客户端断开链接

4、服务器关闭

五、代码实现

服务器端:

// server.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>

#pragma comment(lib,"ws2_32.lib")

#define MAX_NUM 10

typedef struct _client
{
	int port;
	SOCKET socketid;
	HANDLE rev_handle;

}CLIENT_S;


//全局变量区
HANDLE acpt_handle;
CLIENT_S g_client[MAX_NUM];
int g_num = 0;


void init_client()
{
	int i;
	for(i=0; i < MAX_NUM; i++)
	{
		memset(&g_client[i], 0, sizeof(CLIENT_S));
	}
	g_num = 0;
}


void log_print(char *str)
{
	printf("[系统日志]:%s\n", str);
}

void message_print(char *str, int port)
{
	printf("[用户%d]:%s\n", port, str);
}

void del(int index)
{
	int i;
	for(i=index; i< g_num; i++)
	{
		memcpy(&g_client[i], &g_client[i+1], sizeof(CLIENT_S));
	}
	memset(&g_client[g_num-1], 0,  sizeof(CLIENT_S));
	g_num--;
}


int getindex_bysocket(SOCKET s)
{
	int i;
	for(i=0;i<g_num;i++)
	{
		if(g_client[i].socketid == s)
		{
			return i;
		}
	}
	return -1;
}

void send_all(char * sendData)
{
	int i;
	for(i=0;i<g_num;i++)
	{
		send(g_client[i].socketid, sendData, (int)strlen(sendData), 0);
	}
}



int MyRevThread(LPVOID lpParam)
{
    SOCKET sClient = SOCKET(lpParam);


	char revData[255]; 
	char sendData[255];
	int keeplive = true;
	while(keeplive)
	{
		//接收数据
        int ret = recv(sClient, revData, 255, 0);
		//printf("Myret: %d\n",ret);
        if(ret > 0)
        {
            revData[ret] = 0x00;
			int index;
			index = getindex_bysocket(sClient);
			sprintf(sendData, "[用户%d]: %s", g_client[index].port, revData);
			send_all(sendData);
			message_print(revData, g_client[index].port);
        }
		else if(ret == -1)
		{
			int index;
			index = getindex_bysocket(sClient);
			char log[50];
			sprintf(log, "[用户%d] 断开链接!", g_client[index].port);
			log_print(log);

			closesocket(g_client[index].socketid);
			CloseHandle(g_client[index].rev_handle);
			keeplive = false;
			del(index);
			break;
		}
		
	}
    return 0;
}


int MyAcpThread(LPVOID lpParam)
{
	SOCKET slisten = SOCKET(lpParam);

	while(true)
	{
		SOCKET sClient;
		sockaddr_in remoteAddr;
		int nAddrlen = sizeof(remoteAddr);

		sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
		if(sClient == INVALID_SOCKET)
		{
			log_print("接受到一个连接失败...!");
			return -1;
		}
		
		DWORD thID=0;
		HANDLE handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyRevThread, (LPVOID)sClient, 0, (LPDWORD)&thID);
		if(handle == NULL)
		{
			log_print("消息接收线程创建失败!");
			return -1;
		}

		char log[50];
		sprintf(log, "[用户%d] 加入成功! 线程ID %d", remoteAddr.sin_port, thID);
		log_print(log);

		g_client[g_num].port = remoteAddr.sin_port;
		g_client[g_num].rev_handle = handle;
		g_client[g_num].socketid = sClient;
		g_num++;
	}

    return 0;
}



void start()
{
	//初始化WSA
    WORD sockVersion = MAKEWORD(2,2);
    WSADATA wsaData;
    if(WSAStartup(sockVersion, &wsaData)!=0)
    {
        log_print("初始化WSA失败!");
		return;
    }

	log_print("初始化WSA完成!");

	//创建套接字
    SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(slisten == INVALID_SOCKET)
    {
		log_print("创建套接字失败!");
        return;
    }

	log_print("创建套接字完成!");

	//绑定IP和端口
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(8888);;
    sin.sin_addr.S_un.S_addr = INADDR_ANY; 
    if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
		log_print("创建套接字失败!");
		return;
    }

	log_print("绑定IP和端口完成!");

	//开始监听
    if(listen(slisten, 5) == SOCKET_ERROR)
    {
		log_print("开始监听失败!");
        return ;
    }


	HANDLE handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyAcpThread, (LPVOID)slisten, 0, NULL);
	if(handle == NULL)
	{
		log_print("客户端监听线程创建失败!");
		return ;
	}
	acpt_handle = handle;
	log_print("开始监听...等待连接!");

    //closesocket(slisten);
    //WSACleanup();


}

void print_menu()
{
	printf("************************************\n");
	printf("*****************欢迎***************\n");
	printf("************************************\n\n");
	printf("************ 1、查看所有客户端********\n");
	printf("************ 0、关闭服务器并退出*****\n");
	printf("************************************\n");
	
}

void print_client(int index)
{
	printf("************************************\n");
	printf("索引:%d\t", index);
	printf("端口号:%d\t\n", g_client[index].port);
	printf("************************************\n");
}

void print_all()
{
	int optVal;
	int optLen = sizeof(int);


	int i;
	for(i=0;i<g_num;i++)
	{
		print_client(i);
	}
}


int _tmain(int argc, _TCHAR* argv[])
{
	unsigned int ch;
	init_client();
	start();
	while(true)
	{
		print_menu();
		scanf("%u", &ch);
		getchar();
		switch(ch)
		{
			case 0:
				send_all("[系统消息]:服务器关闭,再见!");
				printf("退出!\n");
                WSACleanup();
				exit(0);
				break;
			case 1:
				print_all();
				break;

			default:
				printf("输入错误,重新选择!\n");
				break;
		}

	}
	return 0;
}

客户端:

// client.cpp

#include <WINSOCK2.H>
#include <STDIO.H>
 
#pragma  comment(lib,"ws2_32.lib")
 

int MyRevThread(LPVOID lpParam)
{
    SOCKET sClient = SOCKET(lpParam);
	char revData[255]; 
	int keeplive = true;
	while(keeplive)
	{
		//接收数据
        int ret = recv(sClient, revData, 255, 0);
        if(ret > 0)
        {
            revData[ret] = 0x00;
			printf("\n收到一条消息:\n");
			printf(revData);
			printf("\n");
        }
		else if(ret == -1)
		{
			char * log="";

			closesocket(sClient);
			keeplive = false;
			break;
		}
		
	}
    return 0;
}



int main(int argc, char* argv[])
{
	unsigned int port = 0;
	printf("输入服务器端口号:");
	scanf("%u", &port);


    WORD sockVersion = MAKEWORD(2,2);
    WSADATA data; 
    if(WSAStartup(sockVersion, &data) != 0)
    {
        return 0;
    }
 
    SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sclient == INVALID_SOCKET)
    {
        printf("invalid socket !");
        return 0;
    }
 
    sockaddr_in serAddr;
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(port);
    serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); 
    if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
    {
        printf("connect error !");
        closesocket(sclient);
        return 0;
    }

	HANDLE handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyRevThread, (LPVOID)sclient, 0, NULL);
	if(handle == NULL)
	{
		printf("消息接收线程创建失败!");
		return -1;
	}
	printf("连接服务器成功!\n");

	while(1)
	{
		char input[30]="";

		scanf("%s", &input);

		char * sendData = input;
		send(sclient, sendData, strlen(sendData), 0);
	}

    closesocket(sclient);
    WSACleanup();
    return 0;
}

       搞起!


原创文章,转载请注明出处: https://jerrycoding.com/article/socket-chat

微信
支付宝

您尚未登录,暂时无法评论。请先 登录 或者 注册

0 人参与 | 0 条评论