腾讯云、华为云等可以申请个人云服务器,一般为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时,并无丢包。其实没有丢包也是我发送报文间隔大的原因,如果我把间隔设小,还是会或多或少的丢包的,毕竟是免费的低配置云虚拟机,还要什么自行车呢?