一、关于socket通信

服务器端工作流程:

  • 调用 socket() 函数创建套接字 用 bind() 函数将创建的套接字与服务端IP地址绑定
  • 调用listen()函数监听socket() 函数创建的套接字,等待客户端连接 当客户端请求到来之后
  • 调用 accept()函数接受连接请求,返回一个对应于此连接的新的套接字,做好通信准备
  • 调用 write()/read() 函数和 send()/recv()函数进行数据的读写,通过 accept() 返回的套接字和客户端进行通信 关闭socket(close)

客户端工作流程:

  • 调用 socket() 函数创建套接字
  • 调用 connect() 函数连接服务端
  • 调用write()/read() 函数或者 send()/recv() 函数进行数据的读写
  • 关闭socket(close)

二、用select实现服务器端编程:

select函数楼主在之前文章中(select函数用法)已经提及,不在多做缀述。下面贴上服务器端代码servce.c

#include <stdio.h>
#include <netinet/in.h>   //for souockaddr_in
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <stdlib.h>#include <arpa/inet.h>//for select
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>#include <strings.h>   //for bzero
#include <string.h>#define BUFF_SIZE 1024
#define backlog 7
#define ser_port 11277
#define CLI_NUM 3int client_fds[CLI_NUM];int main(int agrc,char **argv)
{int ser_souck_fd;int i;   char input_message[BUFF_SIZE];char resv_message[BUFF_SIZE];struct sockaddr_in ser_addr;ser_addr.sin_family= AF_INET;    //IPV4ser_addr.sin_port = htons(ser_port); ser_addr.sin_addr.s_addr = INADDR_ANY;  //指定的是所有地址//creat socketif( (ser_souck_fd = socket(AF_INET,SOCK_STREAM,0)) < 0 ){perror("creat failure");return -1;} //bind soucketif(bind(ser_souck_fd, (const struct sockaddr *)&ser_addr,sizeof(ser_addr)) < 0){perror("bind failure");return -1;}//listenif(listen(ser_souck_fd, backlog) < 0) {perror("listen failure"); return -1;}//fd_setfd_set ser_fdset;int max_fd=1;struct timeval mytime;printf("wait for client connnect!\n");while(1){mytime.tv_sec=27;mytime.tv_usec=0;FD_ZERO(&ser_fdset);//add standard inputFD_SET(0,&ser_fdset);if(max_fd < 0){max_fd=0; }//add serverceFD_SET(ser_souck_fd,&ser_fdset);if(max_fd < ser_souck_fd){max_fd = ser_souck_fd;}//add client for(i=0;i<CLI_NUM;i++)  //用数组定义多个客户端fd{if(client_fds[i]!=0) {FD_SET(client_fds[i],&ser_fdset);if(max_fd < client_fds[i]){max_fd = client_fds[i]; }}}//select多路复用int ret = select(max_fd + 1, &ser_fdset, NULL, NULL, &mytime);if(ret < 0)    {    perror("select failure\n");    continue;    }    else if(ret == 0){printf("time out!");continue;}else{if(FD_ISSET(0,&ser_fdset)) //标准输入是否存在于ser_fdset集合中(也就是说,检测到输入时,做如下事情){printf("send message to");bzero(input_message,BUFF_SIZE);fgets(input_message,BUFF_SIZE,stdin);for(i=0;i<CLI_NUM;i++){if(client_fds[i] != 0){printf("client_fds[%d]=%d\n", i, client_fds[i]);send(client_fds[i], input_message, BUFF_SIZE, 0);}}}if(FD_ISSET(ser_souck_fd, &ser_fdset)) {struct sockaddr_in client_address;socklen_t address_len;int client_sock_fd = accept(ser_souck_fd,(struct sockaddr *)&client_address, &address_len);if(client_sock_fd > 0){int flags=-1;//一个客户端到来分配一个fd,CLI_NUM=3,则最多只能有三个客户端,超过4以后跳出for循环,flags重新被赋值为-1for(i=0;i<CLI_NUM;i++){if(client_fds[i] == 0){flags=i; client_fds[i] = client_sock_fd;break;}}if (flags >= 0){printf("new user client[%d] add sucessfully!\n",flags);}else //flags=-1{   char full_message[]="the client is full!can't join!\n";bzero(input_message,BUFF_SIZE);strncpy(input_message, full_message,100);send(client_sock_fd, input_message, BUFF_SIZE, 0);}}    }}//deal with the messagefor(i=0; i<CLI_NUM; i++){if(client_fds[i] != 0){if(FD_ISSET(client_fds[i],&ser_fdset)){bzero(resv_message,BUFF_SIZE);int byte_num=read(client_fds[i],resv_message,BUFF_SIZE);if(byte_num > 0){printf("message form client[%d]:%s\n", i, resv_message);}else if(byte_num < 0){printf("rescessed error!");}//某个客户端退出else  //cancel fdset and set fd=0{printf("clien[%d] exit!\n",i);FD_CLR(client_fds[i], &ser_fdset);client_fds[i] = 0;// printf("clien[%d] exit!\n",i);continue;  //这里如果用break的话一个客户端退出会造成服务器也退出。  }}}}    }return 0;
}

select实现多路复用,多路复用,顾名思义,就是说各做各的事,标准输入事件到来,有相关函数处理。服务器处理服务器的事件,客户端到来时有相关函数对其进行处理,通过select遍历各fd的读写情况,就不用担心阻塞了。

三、用epoll实现客户端编程:

1、客户端程序(epoll_client.c):

#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>    #include <sys/epoll.h>
#include <errno.h>
#include <fcntl.h>#define BUFFER_SIZE 1024    int main(int argc, const char * argv[])
{   int i,n;int connfd,sockfd;struct epoll_event ev,events[20]; //ev用于注册事件,数组用于回传要处理的事件int epfd=epoll_create(256);//创建一个epoll的句柄,其中256为你epoll所支持的最大句柄数struct sockaddr_in client_addr;struct sockaddr_in server_addr;    server_addr.sin_family = AF_INET;    server_addr.sin_port = htons(11277);    server_addr.sin_addr.s_addr =INADDR_ANY;    bzero(&(server_addr.sin_zero), 8);    int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);  ev.data.fd=server_sock_fd;//设置与要处理的事件相关的文件描述符ev.events=EPOLLIN|EPOLLET;//设置要处理的事件类型epoll_ctl(epfd,EPOLL_CTL_ADD,server_sock_fd,&ev);//注册epoll事件if(server_sock_fd == -1)    {    perror("socket error");    return 1;    }    char recv_msg[BUFFER_SIZE];    char input_msg[BUFFER_SIZE];    if(connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) == 0)    {    for(;;){int nfds=epoll_wait(epfd,events,20,500);//等待epoll事件的发生for(i=0;i<nfds;++i){    if(events[i].events&EPOLLOUT) //有数据发送,写socket{bzero(input_msg, BUFFER_SIZE);    fgets(input_msg, BUFFER_SIZE, stdin);    sockfd = events[i].data.fd;write(sockfd, recv_msg, n);ev.data.fd=sockfd;ev.events=EPOLLIN|EPOLLET;epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);}   else if(events[i].events&EPOLLIN)//有数据到来,读socket{bzero(recv_msg, BUFFER_SIZE);if((n = read(server_sock_fd, recv_msg, BUFFER_SIZE)) <0 ){printf("read error!");}ev.data.fd=server_sock_fd;ev.events=EPOLLOUT|EPOLLET;printf("%s\n",recv_msg);}}        }}    return 0;
}

2、关于epoll函数:

相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:
#define __FD_SETSIZE 1024
表示select最多同时监听1024个fd

一共三个函数:

1、 int epoll_create (int size);
创建一个epoll的句柄

size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

2、 int epoll_ctl (int epfd , int op, int fd, struct epoll_event *event);

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fd到epfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:从epfd中删除一个fd;

第三个参数是需要监听的fd

第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

struct epoll_event{__uint32_t events;    /* Epoll events */epoll_data_t data;     /* User data variable */
};

  

typedef union epoll_data{void *ptr;int fd;__uint32_t u32;__uint64_t u64;
} epoll_data_t;

events可以是以下几个宏的集合:

  • EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
  • EPOLLOUT:表示对应的文件描述符可以写;
  • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
  • EPOLLERR:表示对应的文件描述符发生错误;
  • EPOLLHUP:表示对应的文件描述符被挂断;
  • EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
3、 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

等待事件的产生,类似于select()调用。

参数events用来从内核得到事件的集合,

maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,

参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

使用步骤:

<1>首先通过create_epoll(int maxfds)来创建一个epoll的句柄,其中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。

<2>然后每一帧的调用epoll_wait (int epfd, epoll_event events, int max events, int timeout) 来查询所有的网络接口。

<3>kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件范围,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则返回。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。 epoll_wait返回之后应该是一个循环,遍历所有的事件。

基本上都是如下的框架:

for( ; ; ){nfds = epoll_wait(epfd,events,20,500);for(i=0;i<nfds;++i){if(events[i].data.fd==listenfd) //有新的连接{connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);    //accept这个连接ev.data.fd=connfd;ev.events=EPOLLIN|EPOLLET;epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中}else if( events[i].events&EPOLLIN ) //接收到数据,读socket{n = read(sockfd, line, MAXLINE)) < 0    //读ev.data.ptr = md;     //md为自定义类型,添加数据ev.events=EPOLLOUT|EPOLLET;epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓}else if(events[i].events&EPOLLOUT) //有数据待发送,写socket{struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取数据sockfd = md->fd;send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据ev.data.fd=sockfd;ev.events=EPOLLIN|EPOLLET;epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据}else{//其他的处理}}}

  

转载于:https://www.cnblogs.com/wuyepeng/p/9726771.html

Linux下网络socket编程——实现服务器(select)与多个客户端通信相关推荐

  1. Linux下简单socket编程

    Linux下简单socket编程 socket的英文翻译是接口.插座的意思,很形象,就相当于将两个台电脑用一根线连起来,线的两头分别是插头,插在两台电脑上,借此实现通信. 两台电脑通信,实际上是这两台 ...

  2. Linux下采用ss5搭建sock5服务器使用proxychains进行sock5客户端代理

    Linux下采用ss5搭建sock5服务器&使用proxychains进行sock5客户端代理 1.ss5搭建sock5服务器 1.1下载ss5源码 1.2安装相关yum依赖 1.3编绎安装 ...

  3. Linux下的socket编程学习(TCP)

    1.什么是socket? socket的英文翻译就是接口,插座的意思,很形象,就相当于将2台电脑用一根线连起来,线的两头插在不同的电脑上,借此实现通讯的功能. 两台电脑通信,实际上是这两台电脑上的某个 ...

  4. linux下的socket编程

    1.socket的通信流程 这是socket的通信流程图. 在linux系统中,一切皆文件,socket也被看作是文件. 所以socket的通信可以看作是往socket文件中写入数据和读取数据的过程. ...

  5. linux tcp ip c,Linux下TCP/IP编程--TCP实战(select)

    本文参考自徐晓鑫<后台开发>,记录之. 一.为什么要使用非阻塞I/O之select 初学socket的人可能不爱用select写程序,而习惯诸如connect.accept.recv/re ...

  6. MFC:Socket编程—TCP服务端和多个客户端通信

    前言 MFC是微软基础类库,于 C++ 对于 C语言来说,MFC对于window API ,MFC 就相当于C++,window API 相当于C.MFC 封装了 window API 使用起来更加的 ...

  7. linux c socket编程详解,Linux c 网络socket编程

    #include int main() { int sockfd,new_fd; struct sockaddr_in my_addr; struct sockaddr_in their_addr; ...

  8. Linux下C++ Socket编程实例

    参考文章: https://www.cnblogs.com/wuyepeng/p/9737583.html

  9. Linux下网络编程

    Linux下网络编程初步 Linux以其源代码公开闻名于世,并以其稳定性和可靠性雄霸操作系统领域,在网络应用技术方面使用得更加广泛.很久以来它就是Windows的重要对手之一.随着网络时代的来临,Li ...

最新文章

  1. aosp 本地版本管理_本地代码版本管理
  2. Vue 全家桶 + Express 实现的博客
  3. PyTorch基础-线性回归以及非线性回归-02
  4. 完美解决 bash: hexo: command not found
  5. python编程中的if __name__ == 'main': 的作用和原理[2]
  6. js无限加载分页原理实现
  7. 长春java培训老师
  8. cygwin下各盘挂载点
  9. 哪些话你一开始不信,后来却深信不疑
  10. 卧槽!迅雷的代码竟然被扒了精光!
  11. 鲍尔默先生,请拿出证据
  12. 1.2顺序线性表的归并
  13. 纯CSS在线气泡提示生成工具 - CSS ARROW PLEASE!
  14. Vb与数据库学习总结博客
  15. 信息论——信源信息量和信息熵
  16. 无线网破解 跑字典 EWSA使用教程
  17. php errorcode,errorCode.php
  18. Matlab求集合交集和并集
  19. 电脑突然连不上WiFi?按步骤检查
  20. 2.VIM文本编辑器的下载与使用

热门文章

  1. iOS中几种定时器 - 控制了时间,就控制了一切
  2. perl xml dom中文乱码问题解决
  3. 如何修改Win7开机登陆界面背景图片
  4. ospf-cost-FR选路实验
  5. 什么是单页应用(转)
  6. groupByKey、reduceByKey区别(转)
  7. hadoop启动碰到java.net.UnknownHostException
  8. 《C4.5: Programs for Machine Learning》chaper4实验结果重现
  9. ubuntu16.04 xfce4的鼠标主题设置为oxygen-red、修改文件夹背景颜色、两处系统字体设置、右键菜单添加压缩解压选项
  10. LibSVM 使用错误解决