本篇文章简单的介绍使用C语言:实现Linux 网络通信server端。
1.socket函数
socket函数是网络通信中和实现信息交互的基本函数:
参数domian:选择AF_INET(IPv4协议);
参数type:基于字节流的SOCK_STREAM(TCP),基于报文的SOCK_DGRAM(UDP);
参数protocol:一般设置0,支持一种具体的协议;
函数返回的是一个int 类型的socketfd给用户态,同时在内核态分配一个tcb内存控制块, 用于专门的管理客户端的请求连接:IO可读事件。
2.bind函数
将socket 函数返回的socketfd绑定在固定的端口上,规定具体的网络通信的协议和格式,绑定失败返回-1;
3.listen函数
监听sockfd上的TCP网络连接请求,挑选满足条件的,内核开辟资源(半连接队列,可能导致syn泛洪问题)维持一定数量的连接请求,为后续accept函数做准备。
4.accept函数
从半连接队列里阻塞的等待可读的IO事件发生,然后取出组成全连接队列,完成三次握手,获取客户端的IP以及端口与socketfd 存储的服务端端口和IP进行绑定,根据双方协商好的协议形成一个完整的五元组用于双方的通信,返回clientfd用于触发IO可写事件。
5.recv函数
阻塞的等待clientfd可写IO 事件,可以设置超时时间,接收客户端发送给服务端的字节流信息。
6.send函数
阻塞的等待clientfd可写IO 事件,服务端的发送给客户端的字节流信息。
7.实现单次网络通信代码
#include<stdio.h>
#include <errno.h>
#include<string.h>
#include<sys/socket.h>
#include <netinet/in.h>
int main()
{
int socketfd =socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr_in));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
serveraddr.sin_port=htons(1988);
if(-1==bind(socketfd,(struct sockaddr*)&serveraddr,sizeof(struct sockaddr))){
printf("ERROR BIND %s\n",strerror(errno));
}
listen(socketfd,10);
struct sockaddr_in clientaddr;
socklen_t len=sizeof(clientaddr);
int clientfd=accept(socketfd,(struct sockaddr*)&clientaddr,&len);
char buffer[1024]={0};
int count =recv(clientfd,buffer,1024,0);
printf("recv : %s\n",buffer);
count =send(clientfd,buffer,count,0);
printf("send : %d\n",count);
return 0;
}
专属学习链接:https://xxetb.xetslk.com/s/4cnbDc
8.select/poll/epoll实现IO多路复用代码
一连接一线程:由于内核资源的限制最多可能只能做到10000个连接,且性能堪忧;
select模型:采用IO多路复用的思想解决了连接数量的限制,但存在参数过多,每次IO响应需要将fd_set 集合的所有的IO信息拷贝到内核态,根据响应一次刷新fd_set 的位信息,然后从内核态拷贝到用户态进行轮询所有的fd_set的IO信息,找到发生IO可写事件然后处理,因此性能上存在一定的缺陷。
poll模型:优化select的参数过多的问题,编程相对简单,但本质上和select差别不大。
epoll模型:解决了IO多路复用的性能瓶颈,是一个改变Linux走向的重磅级产品,性能和编写上有了极大的优化,可以做到单线程达到100万并发的恐怖性能(在reactor进行介绍)。内部实现是通过RBtree红黑树管理网络IO集合,用队列管理就绪IO事件,从而达到等待IO事件的集合有序,IO可写事件顺序处理的目的,以此提升网络IO的性能。
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/poll.h>
#include <sys/epoll.h>
void *func_pid(void *arg)
{
int clientfd =*(int*)arg;
while(1)
{
char buff[128]={0};
int count =recv(clientfd,buff,128,0);
if(0==count)
{
break;
}
send(clientfd,buff,count,0);
printf("clientfd: %d,count:%d ,buff :%s\n",clientfd,count,buff);
}
close(clientfd);
}
int main()
{
int sockfd =socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(struct sockaddr_in));
serveraddr.sin_family =AF_INET;
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
serveraddr.sin_port=htons(2048);
if(-1==bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(struct sockaddr)))
{
perror("bind");
return -1;
}
listen(sockfd,10);
#if 0
//local addr
struct sockaddr_in clientaddr;
socklen_t len =sizeof(clientaddr);
int clienfd =accept(sockfd,(struct sockaddr*)&clientaddr,&len);
printf("accept\n");
#if 0
char buff[128]={0};
int count =recv(clienfd,buff,128,0);
send(clienfd,buff,count,0);
printf("sockfd :%d, cliendfd: %d,count:%d ,buff :%s\n",sockfd,clienfd,count,buff);
//现在的问题是只能收发一次,要实现多次的收发该如何编写程序?
//通过阻塞的等待事件的发生来触发recv 从而达到一个连接的持续的收发数据
#else
while(1)
{
char buff[128]={0};
int count =recv(clienfd,buff,128,0);
if(0==count)
{
break;
}
send(clienfd,buff,count,0);
printf("sockfd :%d, cliendfd: %d,count:%d ,buff :%s\n",sockfd,clienfd,count,buff);
}
#endif
#elif 0
//为每一次accpet的发生创建一个线程,
struct sockaddr_in clientaddr;
socklen_t len =sizeof(clientaddr);
int clienfd =accept(sockfd,(struct sockaddr*)&clientaddr,&len);
pthread_t pid;
/*
线程调用函数func_pid,在链接较少的时候或者收发的性能没有要求的时候可以考虑
*/
pthread_create(&pid,NULL,func_pid,&clienfd);
#elif0
//select 的引入在一定程度上缓解了阻塞模式的性能,最大的管理的文件数1024默认,可以更改
//由于进行两次拷贝:从内核态的IO文件拷贝到用户态,导致性能低于poll/epoll
// int nready =select(maxfd,rset,wset,eset,timeout)
//fd_set 管理的文件的队列数组,类型int 与文件描述符一致
fd_set rfds,rset;
FD_ZERO(&rfds);
FD_SET(sockfd,&rfds);
int maxfd=sockfd;
printf("loop\n");
while(1)
{
rset=rfds;//no understant 4.10
int nready= select(maxfd+1,&rset,NULL,NULL,NULL);
if(FD_ISSET(sockfd,&rset))
{
struct sockaddr_in clientaddr;
socklen_t len =sizeof(clientaddr);
int clientfd =accept(sockfd,(struct sockaddr*)&clientaddr,&len);
printf("sockfd: %d\n", clientfd);
FD_SET(clientfd, &rfds);//将处理完成的事件再次加入到队列
maxfd = clientfd;
}
for(int i=sockfd+1;i<=maxfd;i++)//此处用来轮询遍历链接的IO
{
if(FD_ISSET(i,&rset))
{
char buff[128]={0};
int count =recv(i,buff,128,0);
if(0==count)
{
printf("disconnect\n");
FD_CLR(i,&rfds);
close(i);
break;
}
send(i,buff,count,0);
printf(" clientfd: %d,count:%d ,buff :%s\n",i,count,buff);
}
}
}
#elif 0
//poll
struct pollfd fds[1024]={0};
fds[sockfd].fd=sockfd;
fds[sockfd].events=POLLIN;
int maxfd=sockfd;
while(1)
{
int nready=poll(fds,maxfd+1,-1);
if(fds[sockfd].revents&POLLIN)
{
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
printf("sockfd: %d\n", clientfd);
fds[clientfd].fd = clientfd;
fds[clientfd].events = POLLIN;
maxfd = clientfd;
}
for(int i= sockfd+1;i<=maxfd;i++)
{
if(fds[i].revents&POLLIN)
{
char buffer[128] = {0};
int count = recv(i, buffer, 128, 0);
if (count == 0) {
printf("disconnect\n");
fds[i].fd = -1;
fds[i].events = 0;
close(i);
continue;
}
send(i, buffer, count, 0);
printf("clientfd: %d, count: %d, buffer: %s\n", i, count, buffer);
}
}
}
#else
//epoll
int epfd=epoll_create(1);
struct epoll_event ev;
ev.events=EPOLLIN;
ev.data.fd=sockfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);
struct epoll_event events[1024]={0};
while(1)
{
int nready=epoll_wait(epfd,events,1024,-1);
for(int i=0;i<nready;i++)
{
int connectfd=events[i].data.fd;
if(sockfd==connectfd)//将每一个客户端和服务端的IO进行来连接
{
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr,&len);
ev.events=EPOLLIN|EPOLLET;
ev.data.fd=clientfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);
printf("clientfd :%d \n",clientfd);
}else if(events[i].events&EPOLLIN){
char buff[10] = {0};
int count = recv(connectfd, buff, 10, 0);
if (0==count ) {
printf("disconnect\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, connectfd, NULL);
close(i);
continue;
}
send(connectfd, buff, count, 0);
printf("clientfd: %d, count: %d, buffer: %s\n", connectfd, count,buff);
}
}
}
#endif
getchar();
}