【Linux】基于Linux系统的云服务器TCP server编写,多线程并发

腾讯云、华为云等可以申请个人云服务器,一般为15-30天。其作用是使你获得一个公网IP,使得任意设备都可以由这个IP向你发起通讯。同时提供24小时在线的云虚拟机,作为服务器,云虚拟机一般安装linux系统,并能通过SSH远程登陆虚拟机并进行linux下的终端操作。说通俗点就是腾讯给你提供一个安装了linux的虚拟机,主机在腾讯那,并给你提供了固定的公网IP,相当于你有了一个公网IP和一个linux主机,你可以通过类似于“远程桌面”的方式来对这个主机进行操作。
如果想在云服务器上做一个你自己的TCP服务器并开通相应的端口,那么任何TCP client都可以由这个IP和端口跟你的服务器建立连接,也就是说提供了一个“采集”信息的功能,我任何支持TCP且能接入公网的设备都可以向服务器上传、下载数据。在越来越多嵌入式设备支持TCP的时代,这个应用也为物联网提供了便利。

既然是在linux系统下建立服务器,那么在云服务器上的编程和普通的linux网络编程无异,我写了一套简易的服务器,具体功能就是:建立tcp server并不停监听有没有client连进来,若有client连进来则开启一个线程专门为其服务。这就使得一个tcp server可以同时与多个client通讯,只要你主机性能跟得上,可以连很多client,这就像网络游戏一样了,一个区同时有上千人在线。我申请的腾讯云虚拟机只有1核CPU、1G内存,即便如此,也通过了并发1000个clinet不丢包的测试。
废话说多了,直接上代码,只有三个源文件->server.c server.h makefile

server.h

#ifndef SERVER_H
#define SERVER_H

#include <fcntl.h>
#include <arpa/inet.h>
#include <errno.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <pthread.h>

#define SOCKET_MAX_NUM 				5000

typedef struct{
	int socket_id;
	struct sockaddr_in client_info;
}SOCKET_SCB;												//套接字块

/////////////////////////////////////////////////////////////////////
void *thread_socket(void *arg);								//线程函数
void connect_in(int SocketNbr,struct sockaddr* client);		//连入
void connect_out(int SocketNbr,int socket_id);				//连出

/////////////////////////////////////////////////////////////////////


#endif

server.c

#include "server.h"



///////////////////////////////////////////////////////////////
unsigned int SocketUsed = 0;								//使用中的套接字个数

SOCKET_SCB s_scb[SOCKET_MAX_NUM];							//套接字块
pthread_mutex_t mutex;										//线程互斥锁
//////////////////////////////////////////////////////////////



void mutext_init(pthread_mutex_t *mutex)
{
	pthread_mutex_init(mutex,NULL);
}

void SocketSCBInit(void)
{
	int j;
	for(j = 0;j < SOCKET_MAX_NUM; j++)
	{
		s_scb[j].socket_id = -1;
		memset(&s_scb[j].client_info,0,sizeof(struct sockaddr_in));
	}
}

int server_init(char *ip,int port)
{
	int sock = socket(AF_INET,SOCK_STREAM,0);
	int bindnum;
	if(sock < 0)
	{
		return sock;
	}

	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_port = htons(port);
	local.sin_addr.s_addr = inet_addr(ip);

	bindnum = bind(sock,(struct sockaddr *)&local,sizeof(local));
	if(bindnum < 0)
	{
		printf("bind fail\n");
		return bindnum;
	}

	if(listen(sock,SOCKET_MAX_NUM) < 0)
	{
		printf("listen fail\n");
		return -1;
	}
	else
	{
		printf("listen ing ....\n");
	}
	return sock;

}

int main()
{
	int SocketNbr = 0;
	struct sockaddr_in client;
	socklen_t len = sizeof(client);
	
	SocketSCBInit();
	mutext_init(&mutex);

	int listen_sock = server_init("0.0.0.0",8888);

	if(listen_sock < 0)
	{
		printf("server init fail\n");
		return 0;
	}

	while(1)
	{
		//检查是否还有可用套接字
		for(SocketNbr = 0;SocketNbr < SOCKET_MAX_NUM; SocketNbr++)
		{
			if(s_scb[SocketNbr].socket_id < 0)
			{
				break;
			}
		}

		if(SocketNbr < SOCKET_MAX_NUM)
		{
			s_scb[SocketNbr].socket_id = accept(listen_sock,(struct sockaddr*)&client,&len);
			if(s_scb[SocketNbr].socket_id < 0)
			{
				printf("accept error\n");
				s_scb[SocketNbr].socket_id = -1;
				memset(&s_scb[SocketNbr].client_info,0,sizeof(struct sockaddr_in));
			}
			else
			{
				connect_in(SocketNbr,&client);
			}
		}
		else
		{
			//printf("socket connected FULL\n");
		}

	}

}

void *thread_socket(void *arg)
{

	int SocketNbr = arg;
	int socket_id = s_scb[SocketNbr].socket_id;
	int recv_len;
	unsigned char buf[256] = {0};
	
	while(1)
	{
		recv_len = recv(socket_id,buf,sizeof(buf),0);

		if(recv_len <= 0)
		{
			break;
		}

		buf[recv_len] = '\0';
		send(socket_id,buf,recv_len,0);
		
		printf("read info from %d : \n%s\n",socket_id,buf);
	}

	connect_out(SocketNbr,socket_id);
	
}

void connect_in(int SocketNbr,struct sockaddr* client)
{
	pthread_t id;

	pthread_mutex_lock(&mutex);
	SocketUsed++;
	memcpy(&s_scb[SocketNbr].client_info,client,sizeof(struct sockaddr));
	pthread_mutex_unlock(&mutex);

	printf("new dev join -> %d %s\n",s_scb[SocketNbr].socket_id,inet_ntoa(s_scb[SocketNbr].client_info.sin_addr));
	printf("avillable socket_id :%d\n",SOCKET_MAX_NUM - SocketUsed);
	pthread_create(&id,NULL,thread_socket,(void *)SocketNbr);
	pthread_detach(id);
}

void connect_out(int SocketNbr,int socket_id)
{
	printf("dev quit -> %d %s\n",s_scb[SocketNbr].socket_id,inet_ntoa(s_scb[SocketNbr].client_info.sin_addr));
	pthread_mutex_lock(&mutex);
	s_scb[SocketNbr].socket_id = -1;
	memset(&s_scb[SocketNbr].client_info,0,sizeof(struct sockaddr_in));
	SocketUsed--;
	pthread_mutex_unlock(&mutex);

	printf("avillable socket_id :%d\n",SOCKET_MAX_NUM - SocketUsed);
	close(socket_id);
}

makefile

objects = server.o

tcp_server:$(objects)
	gcc -o tcp_server $(objects) -lpthread
server.o:server.h

.PHONY:clean
clean:
	rm $(objects)

程序的大体流程与功能:
1.建立tcp监听;
2.在main里的while中找到未在使用用的套接字,并用此套接字等待accept;
3.一旦有client被accept,则对此client建立一个线程;
4.每个client线程中等待从client中读取数据,读到的数据打印出来,之后原封返回给client;
5.client断开连接后返还此套接字的使用权。

使用TCP_UDP_PerformanceTest软件做压力测试,建立1000个连接,每隔5秒集体向服务器发送报文“start>>>>>>>>>>>>>>>end
start<<<<<<<<<<<<<<<end”
测试结果如下,服务器正常工作
打开服务器,正常建立server后提示listen ing…表示正在监听

在这里插入图片描述
之后开始利用软件建立连接,可以看到每accept一个连接,都会打印出socket_id、接入client的ip以及可用的套接字数量available我在编程时拼错了,不要在意这些细节,毕竟我也是过了六级的。。在这里插入图片描述
当测试结束后,所有连接断开,如下图,打印的退出连接的client的套接字、ip地址,以及可用的套接字个数,连入1000个client后为4000,退出1000个client后当然是设定的上限5000了。
在这里插入图片描述
最后是软件测试图,可以看到1000个client并发通讯tcpserver时,并无丢包。其实没有丢包也是我发送报文间隔大的原因,如果我把间隔设小,还是会或多或少的丢包的,毕竟是免费的低配置云虚拟机,还要什么自行车呢?
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/spiremoon/article/details/102987784
今日推荐