Linux&C语言简单使用多线程实现TCP并发服务器-传输层

多线程实现TCP并发服务器

在这里插入图片描述

前言

服务器模型分为两种,循环服务器、并发服务器。

循环服务器: 服务器在同一时刻只能处理同一个客户端的请求。
并发服务器: 服务器在同一时刻能处理多个客户端的请求。

TCP服务器默认的就是一个循环服务器,因为内部有两个阻塞的函数, accept recv 会相互影响
UDP服务器默认的就是一个并发服务器,因为只有一个阻塞的函数 recvfrom

原理

主线程负责 accept 等待客户端连接,一旦有新的客户端连接到服务器,就立即创建一个子线程,在子线程的线程处理函数中专门处理该客户端的读写请求。

代码实现

服务器—01server.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

#define ERRLOG(errmsg)                                       \
	do                                                       \
	{                                                        \
		printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
		perror(errmsg);                                      \
		exit(-1);                                            \
	} while (0)

//需要将该客户端的 acceptfd 和 网络信息结构体传给子线程
typedef struct
{
	int accept_fd;
	struct sockaddr_in client_addr;
} num_t;

//创建套接字-填充服务器网络信息结构体-绑定-监听	
int socket_bind_listen(const char *argv[]);
//子线程处理
void *task1(void *arg);

int main(int argc, const char *argv[])
{
	//检测命令行参数个数
	if (3 != argc)
	{
		printf("Usage : %s <IP> <PORT>\n", argv[0]);
		exit(-1);
	}

	//创建套接字-填充服务器网络信息结构体-绑定-监听	
	int sockfd=socket_bind_listen(argv);

	//用来保存客户端信息的结构体
	struct sockaddr_in client_addr;
	memset(&client_addr, 0, sizeof(client_addr));
	socklen_t client_addr_len = sizeof(client_addr);

	//传递信息的结构体
	num_t ku;
	memset(&ku, 0, sizeof(ku));
	//创建线程
	pthread_t tid;

	while (1)
	{
		//阻塞等待客户端连接--一旦有客户端连接就会解除阻塞
		ku.accept_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
		if (-1 == ku.accept_fd)
			ERRLOG("accept error");
		//inet_ntoa 将32位网络字节序二进制地址转换成点分十进制的字符串
        //ntohs将无符号2字节整型  网络-->主机
		printf("客户端 (%s:%d) 连接了\n",
				 inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
		//创建子线程处理新客户端的读写请求
		//需要将该客户端的 acceptfd 和 网络信息结构体传给子线程
		ku.client_addr = client_addr;
		if (pthread_create(&tid, NULL, task1, (void *)&ku))
			ERRLOG("create tid2 error");
		//设置线程分离属性,无需手动回收资源
		//分离态的线程结束后资源会被自动回收,这个函数不会阻塞
		pthread_detach(tid);
	}
	//关闭监听套接字  一般不关闭
	close(sockfd);
	return 0;
}

//创建套接字-填充服务器网络信息结构体-绑定-监听	
int socket_bind_listen(const char *argv[])
{
	// 1.创建套接字      //IPV4   //TCP
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd)
		ERRLOG("socket error");

	// 2.填充服务器网络信息结构体
	struct sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(server_addr));//清空
	server_addr.sin_family = AF_INET;//IPV4

	//端口号  填 8888 9999 6789 ...都可以
	//atoi字符串转换成整型数
	//htons将无符号2字节整型  主机-->网络
	server_addr.sin_port = htons(atoi(argv[2]));

	// ip地址 要么是当前Ubuntu主机的IP地址 或者
	//如果本地测试的化  使用  127.0.0.1 也可以
	//inet_addr字符串转换成32位的网络字节序二进制值
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//结构体长度
	socklen_t server_addr_len = sizeof(server_addr);

	// 3.将套接字和网络信息结构体绑定
	if (-1 == bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
		ERRLOG("bind error");

	//将套接字设置成被动监听状态
	if (-1 == listen(sockfd, 10))
		ERRLOG("listen error");
	
	return sockfd;
}

//子线程处理
void *task1(void *arg)
{
	//线程的处理函数
	num_t ku = *(num_t *)arg;
	char buff[128] = {0};//临时存放数据
	int ret = 0;
	//接收客户端发来的数据
	while (1)
	{
		if (0 > (ret= recv(ku.accept_fd, buff,sizeof(buff), 0)))
			ERRLOG("recv error");
		else if (0 == ret)//客户端侧CTRL+C
		{
			printf("客户端 (%s:%d) 断开连接\n",
				   inet_ntoa(ku.client_addr.sin_addr), ntohs(ku.client_addr.sin_port));
			break;
		}
		else
		{
			if (0 == strcmp(buff, "quit"))
			{
				printf("客户端 (%s:%d) 退出了\n",
					   inet_ntoa(ku.client_addr.sin_addr), ntohs(ku.client_addr.sin_port));
				break;
			}
			printf("客户端 (%s:%d) 发来数据:[%s]\n",
				   inet_ntoa(ku.client_addr.sin_addr), ntohs(ku.client_addr.sin_port), buff);

			//组装回复给客户端的应答
			strcat(buff, "---996");
			//回复应答
			if (0 > (ret = send(ku.accept_fd, buff,sizeof(buff), 0)))
				ERRLOG("send error");
		}
	}
	//关闭该客户端的套接字
	close(ku.accept_fd);
	//线程退出
	pthread_exit(NULL);
}

客户端—02client.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>

#define ERRLOG(errmsg) do{\
		printf("%s--%s(%d):", __FILE__, __func__, __LINE__);\
		perror(errmsg);\
		exit(-1);\
	}while(0)

int main(int argc, const char *argv[])
{
	//检测命令行参数个数
	if(3 != argc){
		printf("Usage : %s <IP> <PORT>\n", argv[0]);
		exit(-1);
	}

	//1.创建套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == sockfd)
		ERRLOG("socket error");
	

	//2.填充服务器网络信息结构体
	struct sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	//端口号  填 8888 9999 6789 ...都可以
	server_addr.sin_port = htons(atoi(argv[2]));
	//ip地址 要么是当前Ubuntu主机的IP地址 或者
	//如果本地测试的化  使用  127.0.0.1 也可以
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//结构体长度
	socklen_t server_addr_len = sizeof(server_addr);

	//与服务器建立连接
	if(-1 == connect(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
		ERRLOG("connect error");
	
	printf("---连接服务器成功---\n");
	char buff[128] = {0};
	while(1){
		scanf("%s", buff);
		int ret = 0;
		if(0 > (ret = send(sockfd, buff,sizeof(buff), 0)))
			ERRLOG("send error");
		
		if(0 == strcmp(buff, "quit"))
			break;
		
		if(0 > (ret = recv(sockfd, buff,sizeof(buff), 0)))
			ERRLOG("recv error");
		
		printf("收到服务器回复:[%s]\n", buff);
	}
	//关闭套接字
	close(sockfd);

	return 0;
}

执行结果

在这里插入图片描述

6.非原创

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,理解了你的问题。生产者-消费者是一个经典的线程同步问题,需要使用线程锁和条件变量来实现。 首先,我们需要定义一个缓冲区,用来存储生产者生产的数据。缓冲区可以是一个队列或数组,缓冲区大小可以预先定义。 我们需要两个线程函数:生产者函数和消费者函数。生产者函数负责将数据写入缓冲区,消费者函数负责从缓冲区中读取数据。 我们还需要定义一些变量来实现线程同步,包括互斥锁和条件变量。 下面是一个简单的代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define BUFFER_SIZE 10 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; //定义互斥锁 pthread_cond_t cond_producer = PTHREAD_COND_INITIALIZER; //定义生产者条件变量 pthread_cond_t cond_consumer = PTHREAD_COND_INITIALIZER; //定义消费者条件变量 int buffer[BUFFER_SIZE]; //定义缓冲区 int count = 0; //定义缓冲区计数器 void *producer(void *arg) //生产者函数 { int i; for (i = 0; i < 20; i++) //生产20个数据项 { pthread_mutex_lock(&lock); //加锁 while (count == BUFFER_SIZE) //缓冲区满,等待消费者消费 pthread_cond_wait(&cond_producer, &lock); buffer[count] = i; count++; printf("Producer produced item %d\n", i); pthread_cond_signal(&cond_consumer); //唤醒等待的消费者 pthread_mutex_unlock(&lock); //解锁 } pthread_exit(NULL); } void *consumer(void *arg) //消费者函数 { int i, item; for (i = 0; i < 20; i++) //消费20个数据项 { pthread_mutex_lock(&lock); //加锁 while (count == 0) //缓冲区空,等待生产者生产 pthread_cond_wait(&cond_consumer, &lock); item = buffer[count-1]; count--; printf("Consumer consumed item %d\n", item); pthread_cond_signal(&cond_producer); //唤醒等待的生产者 pthread_mutex_unlock(&lock); //解锁 } pthread_exit(NULL); } int main(int argc, char **argv) { pthread_t tid_producer, tid_consumer; pthread_create(&tid_producer, NULL, producer, NULL); pthread_create(&tid_consumer, NULL, consumer, NULL); pthread_join(tid_producer, NULL); pthread_join(tid_consumer, NULL); return 0; } ``` 以上代码使用了互斥锁和条件变量来实现生产者-消费者同步。具体实现可以参考代码注释。 希望对你有帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值