在网络通信中,大部分情况都会使用TCP协议的网络通信方式,毕竟TCP协议的通信方式安全性高、数据不易丢失、还能远距离传输数据。但TCP协议并不能像UDP协议一样本身就具有并发特性,所以在编写TCP协议网络通信中,通常采用多路复用和多线程/多进程的方式实现服务器的并发,即一个服务器同时服务多个客户端。
多路复用
多路复用的相关函数会在程序中有注释。实现多路复用的程序如下:
头文件com.h
#ifndef TCP_STRUCT_TEST_H
#define TCP_STRUCT_TEST_H
#define SERV_PORT 8900
/*网络中绝不发送浮点型数据
*/
struct msg_struct {
char msgtype;
int temp;
int humidty;
short cnt;
char des[64];
} __attribute__((packed)); /*告诉编译器, 不要对该结构进行任何优化 取消对齐*/
#endif
服务器程序server.h
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "comm.h"
/*
多路复用的实现:用函数select
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
注:fd是文件描述符,是socket、accept函数的返回值
nfds:所有fd中数字最大的那个+1
readfds:读文件的集合。如果你关注某个文件/socket为可读事件,将其加入该集合
writefds:写文件的集合。如果你关注某个文件/socket为可写事件,将其加入该集合
exceptfds:异常的集合。如果你关注某个文件/socket为异常事件,将其加入该集合
注意:集合是双向的参数, 你在调用之前将需要监控的fd加入set,当select返回的时候, 里面的原始数据被抹掉.只保留了发生事件的fd。
timeout:超时时间
当所监控的fd集合,超过一定的时间,仍然没有任何fd发生事件也退出
NULL:永久等待
timeout.sec=timeout.usec=0,表示非阻塞监控
timeout.sec = 5;指定超时时间
返回值:
-1:失败,并设置errno的值
>0:表示发生事件fd的个数
==0:没有fd发生事件, 超时了
void FD_CLR(int fd, fd_set *set); //将fd从set中踢掉
int FD_ISSET(int fd, fd_set *set); //判断fd是否在set中
void FD_SET(int fd, fd_set *set); //将fd加入到set中去
void FD_ZERO(fd_set *set); //清空set
*/
/* 监控的读集合 */
fd_set readfdset;
fd_set readfdset_backup;
int max;
int socket_fds[64];
int socket_top = 0;
int serv_do_client_something(int socket_client)//事件发生后要执行的事情
{
int ret;
struct msg_struct msg_serv;
memset(&msg_serv,0,sizeof(msg_serv));
ret = recv(socket_client,&msg_serv,sizeof(msg_serv),0);
if(ret <0){
perror("recv err:");
return -17;
}else if(ret == 0){
printf("peer close it\n");
close (socket_client);
return 0;
}
printf("serv get data:temp%d hud%d cnt%d des:%s\n",ntohl(msg_serv.temp),ntohl(msg_serv.humidty),ntohs(msg_serv.cnt),msg_serv.des);
msg_serv.cnt = ntohs(msg_serv.cnt);
msg_serv.cnt++;
msg_serv.cnt = htons(msg_serv.cnt);
strcpy(msg_serv.des,"your msg is done");
ret = send(socket_client,&msg_serv,sizeof(msg_serv),0);
if(ret <0){
perror("send err:");
return -18;
}
return 0;
}
int main(void )
{
int ret;
int i;
int socket_serv;
int socket_client;
struct sockaddr_in serv_addr;
struct sockaddr_in client_addr;
socklen_t addrlen;
struct msg_struct msg_serv;
socket_serv = socket(AF_INET, SOCK_STREAM, 0);
if(socket_serv < 0){
perror("serv socket err");
return -12;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = INADDR_ANY; //代码自动获取本机ip地址
ret = bind(socket_serv,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret <0){
perror("bind err");
return -13;
}
ret = listen(socket_serv,5);
if(ret <0){
perror("listen err");
return -14;
}
/*将 socket_serv加入 readfdset*/
FD_SET(socket_serv,&readfdset);
max = socket_serv;
loop:
readfdset_backup = readfdset;
ret = select(max+1,&readfdset_backup,NULL,NULL,NULL);
if(ret <0){
perror("select err");
return -15;
}
else if(ret == 0){
printf("select timeout \n");
int do_something_others(void);
goto loop;
/*代码执行到这里,证明有fd发生事件了*/
FD_ISSET(socket_serv,&readfdset_backup)){
addrlen = sizeof(client_addr);
socket_client = accept(socket_serv,(struct sockaddr *)&client_addr,&addrlen);
if(socket_client <0){
perror("accept err:");
return -16;
}
//保存新的套接字通信
cket_fds[socket_top] = socket_client;
socket_top++;
FD_SET(socket_client,&readfdset); //将新的加入到集合中
max = max > socket_client ? max :socket_client;
printf("serv detect connect peer ip:%s port:%d\n",inet_ntoa(client_addr.sin_addr),ntohs( client_addr.sin_port));
goto loop;
}
for(i=0;i<socket_top;i++){
if(FD_ISSET(socket_fds[i],&readfdset_backup)){
//deal socket
serv_do_client_something(socket_fds[i]);
}
}
goto loop;
/*
注意, 客户端 在socket和connect之间 有一个操作bind
客户端可以指定自己的端口号, 但是如果省去
connect的时候,发现没有端口号,系统自动分配一个.
*/
while(1){
/*使用新的 socket发送和接受
recv
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
buf:接收数据存放的位置
len: buf的大小
flags: 0
返回值
>0 实际接收到的数据
-1 errno
==0 对方主动或者意外挂掉
*/
memset(&msg_serv,0,sizeof(msg_serv));
ret = recv(socket_client,&msg_serv,sizeof(msg_serv),0);
if(ret <0){
perror("recv err:");
return -17;
}else if(ret == 0){
printf("peer close it\n");
close (socket_client);
return 0;
}
printf("serv get data:temp%d hud%d cnt%d des:%s\n",ntohl(msg_serv.temp),ntohl(msg_serv.humidty),ntohs(msg_serv.cnt),msg_serv.des);
msg_serv.cnt = ntohs(msg_serv.cnt);
msg_serv.cnt++;
msg_serv.cnt = htons(msg_serv.cnt);
strcpy(msg_serv.des,"your msg is done");
/*
send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
len: 是你要发送的数据长度
返回值
>=0 实际发送
-1 errno
*/
ret = send(socket_client,&msg_serv,sizeof(msg_serv),0);
if(ret <0){
perror("send err:");
return -18;
}
}
close(socket_client);
}
客户端程序client.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "comm.h"
int main(void )
{
int ret;
int socket_serv;
struct sockaddr_in serv_addr;
struct msg_struct msg_cli;
socket_serv = socket(AF_INET, SOCK_STREAM, 0);
if(socket_serv < 0){
perror("client socket err");
return -12;
}
/*
注意, 客户端 在socket和connect之间 有一个操作bind
客户端可以指定自己的端口号, 但是如果省去
connect的时候,发现没有端口号,系统自动分配一个,一般都不写。
*/
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = inet_addr( "149.28.27.109");
ret = connect(socket_serv,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
if(ret <0){
perror("connect err");
return -12;
}
memset(&msg_cli,0,sizeof(msg_cli));
msg_cli.temp = htonl(28);
msg_cli.humidty = htonl(80);
msg_cli.cnt = htons(0);
msg_cli.msgtype = 12;
strcpy(msg_cli.des,"hello msg come from client");
while(1){
strcpy(msg_cli.des,"hello msg come from client");
ret = send(socket_serv,&msg_cli,sizeof(msg_cli),0);
if(ret <0){
perror("client send err:");
return -18;
}
memset(&msg_cli,0,sizeof(msg_cli));
ret = recv(socket_serv,&msg_cli,sizeof(msg_cli),0);
if(ret <0){
perror("client recv err:");
return -17;
}else if(ret == 0){
printf("peer close it\n");
close (socket_serv);
return 0;
}
printf("client get data:temp%d humm%d cnt%d des:%s\n",ntohl(msg_cli.temp),ntohl(msg_cli.humidty),ntohs(msg_cli.cnt),msg_cli.des);
sleep(1);
}
close(socket_serv);
return 0;
}
多进程并发
多进程并发在嵌入式开发中是最常用的一种服务器并发方式,程序如下:
头文件com.h
#ifndef TCP_STRUCT_TEST_H
#define TCP_STRUCT_TEST_H
#define PORT 7200
struct data_package
{
int temp;
int hum;
short cnt;
char info[64];
}__attribute__((packed));
#endif
服务器程序server.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 <pthread.h>
#include "com.h"
struct pthread_s
{
int server;
pthread_t pid;
};
void *pthread_client(void *pth)
{
struct pthread_s *p = pth;
int resize;
struct data_package data;
int res;
while(1)
{
resize = recv(p->server,&data,sizeof(data),0);
if(resize < 0)
{
perror("recv()");
close(p->server);
return NULL;
}
else if(resize == 0)
{
printf("peer close!\n");
close(p->server);
return NULL;
}
data.temp = ntohl(data.temp);
data.hum = ntohl(data.hum);
data.cnt = ntohs(data.cnt);
printf("[%d] temperture:%d,humidty:%d,%s\n",data.cnt,data.temp,\
data.hum,data.info);
strcpy(data.info,"get your message!");
//data.cnt = ntohs(data.cnt);
data.cnt++;
data.cnt = htons(data.cnt);
data.temp = htonl(data.temp);
data.hum = htonl(data.hum);
res = send(p->server,&data,sizeof(data),0);
if(res < 0)
{
perror("send()");
close(p->server);
return NULL;
}
}
close(p->server);
return NULL;
}
int main(void)
{
int socket_serv;
int res;
int client;
struct sockaddr_in sockaddr;
struct sockaddr_in client_addr;
socklen_t client_len;
pthread_t pid;
struct pthread_s *p;
socket_serv = socket(AF_INET,SOCK_STREAM,0);
if(socket_serv < 0)
{
perror("socket()");
exit(-1);
}
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = PORT;
//sockaddr.sin_addr.s_addr = inet_addr("192.168.3.188");
sockaddr.sin_addr.s_addr = INADDR_ANY;
res = bind(socket_serv,(struct sockaddr *)&sockaddr,sizeof(sockaddr));
if(res < 0)
{
perror("bind()");
exit(-2);
}
res = listen(socket_serv,5);
if(res < 0)
{
perror("listen()");
exit(-3);
}
while(1)
{
client_len=sizeof(client_addr);
client = accept(socket_serv,(struct sockaddr *)&client_addr,\
&client_len);
if(client < 0)
{
perror("accept()");
exit(-4);
}
printf("connect port:%d,address:%s\n",ntohs(client_addr.sin_port),\
inet_ntoa(client_addr.sin_addr));
p=malloc(sizeof(*p));
p->server = client;
pthread_create(&pid,NULL,pthread_client,p);
p->pid = pid;
}
exit(0);
}
客户端程序client.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 "com.h"
#define BUFSIZE 128
int main(void)
{
int socket_client;
int res;
struct sockaddr_in sockaddr;
int resize;
socklen_t client_len;
struct data_package data_c;
socket_client = socket(AF_INET,SOCK_STREAM,0);
if(socket_client < 0)
{
perror("socket()");
exit(-1);
}
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = PORT;
sockaddr.sin_addr.s_addr = inet_addr("192.168.3.188");
res = connect(socket_client,(struct sockaddr *)&sockaddr,\
sizeof(sockaddr));
if(res < 0)
{
perror("bind()");
exit(-2);
}
data_c.temp = 28;
data_c.hum = 59;
data_c.cnt = 0;
while(1)
{
strcpy(data_c.info,"message have sended!");
data_c.temp = htonl(data_c.temp);
data_c.hum = htonl(data_c.hum);
data_c.cnt = htons(data_c.cnt);
res = send(socket_client,&data_c,sizeof(data_c),0);
if(res < 0)
{
perror("send()");
exit(-6);
}
memset(&data_c,0,sizeof(data_c));
resize = recv(socket_client,&data_c,sizeof(data_c),0);
if(resize < 0)
{
perror("recv()");
exit(-5);
}
data_c.temp = ntohl(data_c.temp);
data_c.hum = ntohl(data_c.hum);
data_c.cnt = ntohs(data_c.cnt);
printf("[%d] temperture:%d,humidty:%d,%s\n",data_c.cnt,data_c.temp,\
data_c.hum,data_c.info);
sleep(1);
}
close(socket_client);
exit(0);
}