开发部分:直接上代码(服务端)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc,char* argv[])
{
if (argc <= 2)
{
printf( "Usage: %s ip_address portname\n", argv[0] );
return 0;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
int listenfd = socket( PF_INET, SOCK_STREAM, 0 );//一、创建socket文件描述符
assert( listenfd >= 1 );
struct sockaddr_in address;//socket地址,封装了端口号和ip地址(IPv4)
memset( &address, 0, sizeof( address ) ); //address清零
address.sin_family = AF_INET; //设置address的地址族
address.sin_port = htons( port ); //设置address的端口号
inet_pton( AF_INET, ip, &address.sin_addr ); //设置address的ip地址
int ret = 0;
ret = bind( listenfd, (struct sockaddr*)( &address ),
sizeof( address ) ); //二、绑定socket与socket地址
assert( ret != -1 );
ret = listen( listenfd, 5 ); //三、创建监听队列(以存放待处理的客户连接),指定listenfd为被监听socket
assert( ret != -1 );
struct sockaddr_in client;//存放将被接受的客户socket地址
socklen_t client_addrlength = sizeof( client );
int sockfd = accept( listenfd, (struct sockaddr*)( &client ), &client_addrlength );//四、从监听队列里接受一个连接,返回标识被接受的连接的socket,用于后续通信,存远端socket地址到client
char buf_size[1024] = {0};
int recv_size = 0;
recv_size = recv( sockfd, buf_size, sizeof( buf_size ) , 0);//TCP数据读,返回读取数据的长度
int send_size = 0;
send_size = send( sockfd, buf_size , recv_size , 0 );//TCP数据写,返回读取数据的长度
close( sockfd ); //五、关闭连接
close( listenfd ); //五、关闭监听socket
return 0;
}
测试部分
服务端:
客户端(nc模拟):
很明显上述echo服务器只能处理一次客户连接
IO复用版本:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#define MAX_EVENTS_NUMBER 5
//将fd设置为非阻塞的,并返回旧状态
int set_non_blocking( int fd )
{
int old_state = fcntl( fd, F_GETFL );
int new_state = old_state | O_NONBLOCK;
fcntl( fd, F_SETFL, new_state );
return old_state;
}
//将fd上的EPOLLIN注册到epollfd指示的epoll内核事件表中,并启用EPOLL ET模式,并将fd设置为非阻塞模式
void addfd( int epollfd , int fd )
{
epoll_event event;
event.events = EPOLLIN | EPOLLET;//指定事件
event.data.fd = fd;
epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
set_non_blocking( fd );
}
int main( int argc , char* argv[] )
{
if (argc <= 2)
{
printf( "Usage: %s ip_address portname\n", argv[0] );
return 0;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
int listenfd = socket( PF_INET, SOCK_STREAM, 0 ); //一、创建socket文件描述符
assert( listenfd >= 1 );
struct sockaddr_in address;//socket地址,封装了端口号和ip地址(IPv4)
memset( &address, 0, sizeof( address ) );//address清零
address.sin_family = AF_INET; //设置address的地址族
address.sin_port = htons( port ); //设置address的端口号
inet_pton( AF_INET, ip, &address.sin_addr ); //设置address的ip地址
int ret = 0;
ret = bind( listenfd, (struct sockaddr*)( &address ),
sizeof( address ) ); //二、绑定socket与socket地址
assert( ret != -1 );
ret = listen( listenfd, 5 ); //三、创建监听队列(以存放待处理的客户连接),指定listenfd为被监听socket
assert( ret != -1 );
epoll_event events[ MAX_EVENTS_NUMBER ];//存放就绪事件的数组
int epollfd = epoll_create( 5 ); //四、创建文件描述符,来标识内核中的事件表
assert( epollfd != -1);
addfd( epollfd, listenfd ); //五、注册listenfd到事件表中,监听其可读事件(对其可读事件进行处理)
while(1) //六、以事件为驱动,处理各类可读事件(包括接受新连接)
{
int number = epoll_wait( epollfd, events, MAX_EVENTS_NUMBER, -1 );//返回就绪fd个数
if( number < 0 )
{
printf( "epoll_wait failed\n" );
return -1;
}
for( int i = 0; i < number; ++i )
{
const auto& event = events[i];
const auto eventfd = event.data.fd;
if( eventfd == listenfd ) //6.1 说明监听队列里有新客户连接
{ //接受新连接,并将新连接注册到事件表中
struct sockaddr_in client;
socklen_t client_addrlength = sizeof( client );
int sockfd = accept( listenfd, ( struct sockaddr* )( &address ),
&client_addrlength );
addfd( epollfd, sockfd );//将新连接注册到事件表中,监听其可读事件
}
else if( event.events & EPOLLIN ) //6.2 处理客户连接的可读事件
{
char buf[1024] = {0};
while(1)
{
memset( buf, '\0', sizeof( buf ) );
int recv_size = recv( eventfd, buf, sizeof( buf ), 0 );//TCP数据读,返回读取数据的长度
if( recv_size < 0 )//错误处理
{
if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
{
break;
}
printf(" sockfd %d,recv msg failed\n", eventfd );
close( eventfd );
break;
}
else if( recv_size == 0)
{
close( eventfd );
break;
}
else
{
send( eventfd, buf, recv_size, 0 );//TCP数据写
}
}
}
}
}
close( listenfd );//五、关闭监听socket
return 0;
}
测试部分
服务端:
2个客户端(nc模拟):