在socket网络编程中,如果当前已经有连接了,那么另外一个请求想连接服务器,只能等待了。
因此解决的办法有下面4中。
fork的方式
这种方式很好理解,
代码如下
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8111
#define MESSAGE_SIZE 1024
int main(){
int ret = -1;
int pid;
int socket_fd = -1;
int accept_fd = -1;
int curpos = 0;
int backlog = 10;
int flag = 1;
struct sockaddr_in local_addr, remote_addr;
//create a tcp socket
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if ( socket_fd == -1 ){
perror("create socket error");
exit(1);
}
//set option of socket
ret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
if ( ret == -1 ){
perror("setsockopt error");
}
//set local address
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(PORT);
local_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(local_addr.sin_zero), 8);
//bind socket
ret = bind(socket_fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in));
if(ret == -1 ) {
perror("bind error");
exit(1);
}
ret = listen(socket_fd, backlog);
if ( ret == -1 ){
perror("listen error");
exit(1);
}
for(;;){
int addr_len = sizeof( struct sockaddr_in );
//accept an new connection, block......
accept_fd = accept( socket_fd, (struct sockaddr *)&remote_addr, &addr_len );
//create a sub process
pid = fork();
//子进程
if( pid==0 ){
char in_buf[MESSAGE_SIZE] = {0,};
for(;;){
memset(in_buf, 0, MESSAGE_SIZE);
ret = recv(accept_fd ,&in_buf, MESSAGE_SIZE, 0);
if(ret == 0){
break;
}
printf( "receive message:%s\n", in_buf );
send(accept_fd, (void*)in_buf, MESSAGE_SIZE, 0);
}
printf("close client connection...\n");
close(accept_fd);
}
//parent process
}
if(pid != 0 ){
printf("quit server...\n");
close(socket_fd);
}
return 0;
}
select的方式
需要先了解异步IO,
使用select的例子,如下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8888
#define FD_SIZE 1024
#define MESSAGE_SIZE 1024
int main(){
int ret = -1;
int pid;
int accept_fd = -1;
int socket_fd = -1;
int accept_fds[FD_SIZE] = {-1, };
int curpos = -1;
int maxpos = 0;
int backlog = 10;
int flags = 1; //open REUSEADDR option
int max_fd = -1;
fd_set fd_sets;
int events=0;
struct sockaddr_in local_addr, remote_addr;
//create a tcp socket
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if ( socket_fd == -1 ){
perror("create socket error");
exit(1);
}
//set option of socket
ret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags));
if ( ret == -1 ){
perror("setsockopt error");
}
//NONBLOCK
flags = fcntl(socket_fd, F_GETFL, 0);
fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK);
//set local address
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(PORT);
local_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(local_addr.sin_zero), 8);
//bind socket
ret = bind(socket_fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in));
if(ret == -1 ) {
perror("bind error");
exit(1);
}
ret = listen(socket_fd, backlog);
if ( ret == -1 ){
perror("listen error");
exit(1);
}
max_fd = socket_fd; //每次都重新设置 max_fd
for(int i=0; i< FD_SIZE; i++){
accept_fds[i] = -1;
}
for(;;) {
FD_ZERO(&fd_sets); //清空sets
FD_SET(socket_fd, &fd_sets); //将socket_fd 添加到sets
for(int k=0; k < maxpos; k++){
if(accept_fds[k] != -1){
if(accept_fds[k] > max_fd){
max_fd = accept_fds[k];
}
printf("fd:%d, k:%d, max_fd:%d\n", accept_fds[k], k, max_fd);
FD_SET(accept_fds[k], &fd_sets); //继续向sets添加fd
}
}
//遍历所有的fd
events = select( max_fd + 1, &fd_sets, NULL, NULL, NULL );
if(events < 0) {
perror("select");
break;
}else if(events == 0){
printf("select time out ......");
continue;
}else if( events ){
printf("events:%d\n", events);
if( FD_ISSET(socket_fd, &fd_sets)){ // 如果来的是新连接
printf("listen event :1\n");
int a = 0;
for( ; a < FD_SIZE; a++){
if(accept_fds[a] == -1){
curpos = a;
break;
}
}
if(a == FD_SIZE){
printf("the connection is full!\n");
continue;
}
int addr_len = sizeof( struct sockaddr_in );
accept_fd = accept(socket_fd, (struct sockaddr *)&remote_addr, &addr_len); //创建一个新连接的fd
int flags = fcntl(accept_fd, F_GETFL, 0); //取出新连接的 fd 的相关选项
fcntl(accept_fd, F_SETFL, flags|O_NONBLOCK); //设置为非阻塞
accept_fds[curpos] = accept_fd;
if(curpos+1 > maxpos){
maxpos = curpos + 1;
}
if(accept_fd > max_fd){
max_fd = accept_fd;
}
printf("new connection fd:%d, curpos = %d \n",accept_fd, curpos);
}
for(int j=0; j < maxpos; j++ ){
if( (accept_fds[j] != -1) && FD_ISSET(accept_fds[j], &fd_sets)){ //有事件时
printf("accept event :%d, accept_fd: %d\n",j, accept_fds[j]);
char in_buf[MESSAGE_SIZE];
memset(in_buf, 0, MESSAGE_SIZE);
int ret = recv(accept_fds[j], &in_buf, MESSAGE_SIZE, 0);
if(ret == 0){
close(accept_fds[j]);
accept_fds[j] = -1;
}
printf( "receive message:%s\n", in_buf );
send(accept_fds[j], (void*)in_buf, MESSAGE_SIZE, 0);
}
}
}
}
printf("quit server...\n");
close(socket_fd);
return 0;
}
epoll方式
代码例子,如下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8111
#define FD_SIZE 20
#define MAX_EVENTS 20
#define TIME_OUT 500
#define MESSAGE_SIZE 1024
#define NB_PROCESS 4
int main(){
int ret = -1;
int socket_fd = -1;
int accept_fd = -1;
int flags = 1;
int backlog = 10;
struct sockaddr_in local_addr,remote_addr;
struct epoll_event ev, events[FD_SIZE];
int epoll_fd = -1;
int event_number = 0;
int pid;
int status;
int max_subprocess = NB_PROCESS;
//creat a tcp socket
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if ( socket_fd == -1 ){
perror("create socket error");
exit(1);
}
//set REUSERADDR
ret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&flags, sizeof(flags));
if ( ret == -1 ){
perror("setsockopt error");
}
//set NONBLOCK
flags = fcntl(socket_fd, F_GETFL, 0);
fcntl(socket_fd, F_SETFL, flags|O_NONBLOCK);
//set address
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(PORT);
local_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(local_addr.sin_zero),8);
//bind addr
ret = bind(socket_fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in));
if( ret == -1 ) {
perror("bind error");
exit(1);
}
if (listen(socket_fd, backlog) == -1 ){
perror("listen error");
exit(1);
}
//fork some subprocess
for(int a=0; a < max_subprocess; a++){
if(pid !=0){
pid = fork();
}
}
//child process
if(pid == 0) {
printf("create an new child process...");
//create epoll
epoll_fd = epoll_create(256);//the size argument is ignored
ev.data.fd=socket_fd;
ev.events=EPOLLIN;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &ev); //将socket_fd 添加到epoll中
for(;;){
//events 表示一共有多少事件被侦听
//MAX_EVENTS 表示在events个事件中,本次调用最多能返回多少个被解发的事件
//TIME_OUT 表示本次调用最多等多长时间
//event_number 表示本次调用真正有多少事件被解发
event_number = epoll_wait(epoll_fd, events, MAX_EVENTS, TIME_OUT);
for(int i=0; i < event_number; i++){
if(events[i].data.fd == socket_fd){ // 如果是侦听端口的事件
printf("listen event... \n");
int addr_len = sizeof( struct sockaddr_in );
accept_fd = accept(socket_fd, (struct sockaddr *)&remote_addr, &addr_len);
//将新创建的socket设置为 NONBLOCK 模式
flags = fcntl(accept_fd, F_GETFL, 0);
fcntl(accept_fd, F_SETFL, flags|O_NONBLOCK);
ev.data.fd=accept_fd;
ev.events=EPOLLIN | EPOLLET;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accept_fd, &ev);
printf("new accept fd:%d\n",accept_fd);
} else if(events[i].events & EPOLLIN){
//printf("accept event :%d\n",i);
char in_buf[MESSAGE_SIZE];
memset(in_buf, 0, MESSAGE_SIZE);
//receive data
ret = recv( events[i].data.fd, &in_buf, MESSAGE_SIZE, 0 );
if(ret == MESSAGE_SIZE ){
printf("maybe have data....");
}
if(ret <= 0){
switch (errno){
case EAGAIN:
ret = recv(events[i].data.fd, &in_buf, MESSAGE_SIZE, 0);
break;
case EINTR:
printf("recv EINTR... \n");
break;
default:
printf("the client is closed, fd:%d\n", events[i].data.fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, &ev);
close(events[i].data.fd);
;
}
}
printf(">>>receive message:%s\n", in_buf);
send(events[i].data.fd, &in_buf, ret, 0);
}
}
}
}else {// pid == 0
//wait child process to quit
wait(&status);
}
return 0;
}
对应客户端的代码:
如下:
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#define MAX 80
#define PORT 9876
#define SA struct sockaddr
void func(int sockfd)
{
char buff[MAX];
int n;
for (;;) {
bzero(buff, sizeof(buff));
printf("Enter the string : ");
n = 0;
while ((buff[n++] = getchar()) != '\n')
;
write(sockfd, buff, sizeof(buff));
bzero(buff, sizeof(buff));
read(sockfd, buff, sizeof(buff));
printf("From Server : %s", buff);
if ((strncmp(buff, "exit", 4)) == 0) {
printf("Client Exit...\n");
break;
}
}
}
int main()
{
int sockfd, connfd, ret;
struct sockaddr_in servaddr, cli;
// socket create and varification
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket creation failed...\n");
exit(0);
}
else
printf("Socket successfully created..\n");
bzero(&servaddr, sizeof(servaddr));
// assign IP, PORT
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(PORT);
// connect the client socket to server socket
ret = connect(sockfd, (SA*)&servaddr, sizeof(servaddr));
if (ret != 0) {
printf("connection with the server failed...\n");
exit(0);
}
else
printf("connected to the server..\n");
// function for chat
func(sockfd);
// close the socket
close(sockfd);
}
利用IO事件处理库
epoll使用起来比较复杂,很多开源项目对epoll进行了封装,如libevent、libuv等,这都是比较著名的开源事件处理库,
总结
epoll的优点:
(1)epoll监视的文件描述符不受限制,内存越大其值越大,而select最多是2048个;
(2)epoll不会随着监视的数量增大,而效率降低。
参考:
Linux IO模式及 select、poll、epoll详解,这是一个非常好的文章,详细介绍了I/O阻塞和I/O非阻塞,I/O同步和I/O异步的区别。