C语言 socket学习整理
分三个topic来熟悉C语言的socket使用方法。
- 一台client与一台server之间的双向TCP通讯。
- 使用select接口实现的多台client与一台server之间的通讯。
- 使用epoll接口实现的多台client与一台server之间的通讯。
TCP通信模型与UDP通信模型的区别
- UDP通信模型中,在通信开始之前,不需要建立相关的链接,只需要发送数据即可,类似于生活中,“写信"”
- TCP通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,“打电话”"
这里仅以TCP通信为例。先放上TCP协议socket的编程流程图:
在代码调试过程中遇到的知识点整理如下:
- 客户端可以用bind来绑定IP与端口,但不是必须的。
-
accept函数的第三个参数,在调用之前需要设置为第二个参数的长度,否则会出现得到的客户端IP地址/端口为0.0.0.0:0。
struct sockaddr_in client_address = {0}; socklen_t address_len = sizeof(client_address); int client_sock_fd = accept(listen_fd, (struct sockaddr *)&client_address, &address_len);
-
socket函数的第三个参数protocol。protocol:指定要与socket一起使用的特定协议,一般可以置为0,表示使用默认的常用的也就是sock_stream,流式传输,默认是TCP协议;sock_dgram,数据报形式,默认是UDP协议。
-
select的实现是通过对设备的轮询来实现的,每次调用FD_ISSET()函数后 ,会把原来待检测的但是仍没就绪的描述字清0了。所以,每次调用select()前要重新调用FD_SET()来设置一下待检测的描述设备。
一台client与一台server之间的双向TCP通讯
client端代码如下:
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h> // for close
#include <arpa/inet.h>
#include <pthread.h>
#define SERVPORT 3333
#define MAXDATASIZE 100
static void dump_data(char* name,char* buf,int len)
{
int i;
printf("buf:");
for(i=0; i<len; i++){
if(i%8 == 0)
printf("n");
printf("%4d ",buf[i]);
}
printf("n");
}
static void *thread(void *param1)
{
char buf[MAXDATASIZE];
int buf_size = 0;
int new_fd = *((int*)param1);
while(1){
buf_size = recv(new_fd,buf,MAXDATASIZE+1,0);
printf("buf_size:%dn",buf_size);
if(buf_size <= 0 )
break;
printf("client received:%sn",buf);
//dump_data("buf",buf,buf_size);
usleep (10000);
}
return NULL;
}
int main(int argc,char *argv[])
{
int sockfd,sendbytes;
char buf[MAXDATASIZE+1];
char **pptr;
//char buf_num[6] = {0,};
//char str[6] = {0,};
//int num = 0;
struct hostent *host;
struct sockaddr_in serv_addr;
struct sockaddr_in clientAddr;//客户端地址
char ipAddress[INET_ADDRSTRLEN];//保存点分十进制的ip地址
socklen_t clientAddrLen = sizeof(clientAddr);
if(argc < 2){
fprintf(stderr,"Please enter the server's hostname!n");
exit(1);
}
/*地址解析函数*/
if((host=gethostbyname(argv[1]))==NULL){
perror("gethostbyname");
exit(1);
}
//strcpy(str,argv[2]);
/*创建socket*/
if((sockfd=socket(AF_INET,SOCK_STREAM,0))== -1){
perror("socket");
exit(1);
}
printf("client sockfd:%dn",sockfd);
/*设置sockaddr_in 结构体中相关参数*/
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERVPORT);
serv_addr.sin_addr=*((struct in_addr *)host->h_addr);
bzero(&(serv_addr.sin_zero),8);
/*调用connect函数主动发起对服务器端的连接*/
if(connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr))== -1){
perror("connect");
exit(1);
}
getsockname(sockfd, (struct sockaddr*)&clientAddr, &clientAddrLen);//获取sockfd表示的连接上的本地地址
printf("client address = %s:%dn", inet_ntop(AF_INET, &clientAddr.sin_addr, ipAddress, sizeof(ipAddress)),
ntohs(clientAddr.sin_port));
printf("official hostname:%sn", host->h_name); //主机规范名
for(pptr = host->h_aliases; *pptr != NULL; pptr++) //将主机别名打印出来
printf("alias: %sn", *pptr);
//新建一个线程来进行发送操作.
pthread_t ntid;
pthread_create(&ntid,NULL,thread,&sockfd);
/*发送消息给服务器端*/
while(1){
//scanf("%s",buf);
ssize_t size = read(STDIN_FILENO, buf, sizeof(buf));
if(!strcmp(buf,"quit"))
break;
printf("client send:%s size:%dn",buf,size);
if(size > 0){
if((sendbytes=send(sockfd,buf,size,0))== -1){
perror("send");
break;
}
}
usleep (10000);
//sleep(2);
}
close(sockfd);
return 0;
}
server端代码如下:
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#define SERVPORT 3333
#define BACKLOG 10
#define MAX_CONNECTED_NO 10
#define MAXDATASIZE 100
char recv_buf[MAXDATASIZE+1];
static void dump_data(char* name,char* buf,int len)
{
int i;
printf("buf:");
for(i=0; i<len; i++){
if(i%8 == 0)
printf("n");
printf("%4d ",buf[i]);
}
printf("n");
}
void* server_send(void* param)
{
int new_fd = *(int*)param;
while(1){
bzero(recv_buf, MAXDATASIZE + 1);
ssize_t size = read(STDIN_FILENO, recv_buf, sizeof(recv_buf));
printf("recv_buf size:%d size:%dn",sizeof(recv_buf),size);
printf("server send %sn",recv_buf);
if(size > 0){
int len = send(new_fd, recv_buf,size, 0);
if(len <= 0)
break;
else{
; //dump_data("recv_buf",recv_buf,sizeof(recv_buf));
}
}
}
exit(0);
}
int main()
{
struct sockaddr_in server_sockaddr,client_sockaddr,peerAddr;
struct sockaddr listendAddr,connectedAddr;
//int recvbytes;
socklen_t sin_size;
//socklen_t peerLen;
int sockfd,client_fd;
//char ipAddr[INET_ADDRSTRLEN];//保存点分十进制的地址
//fd_set readfd;
//fd_set writefd;
/*建立socket连接*/
if((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1){
perror("socket");
exit(1);
}
printf("server sockfd=%dn",sockfd);
/*设置sockaddr_in 结构体中相关参数*/
server_sockaddr.sin_family=AF_INET;
server_sockaddr.sin_port=htons(SERVPORT);
server_sockaddr.sin_addr.s_addr=INADDR_ANY;
bzero(&(server_sockaddr.sin_zero),8);
/*绑定函数bind*/
if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))== -1){
perror("bind");
exit(1);
}
printf("bind success!n");
/*调用listen函数*/
if(listen(sockfd,BACKLOG)== -1){
perror("listen");
exit(1);
}
printf("listening....n");
socklen_t listendAddrLen = sizeof(listendAddr);
getsockname(sockfd, (struct sockaddr *)&listendAddr, &listendAddrLen);//获取监听的地址和端口
struct sockaddr_in *tmp_sockaddr;
tmp_sockaddr = (struct sockaddr_in *)&listendAddr;
printf("listen address = %s:%dn", inet_ntoa(tmp_sockaddr->sin_addr), ntohs(tmp_sockaddr->sin_port));
if((client_fd=accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size))== -1){
perror("accept");
exit(1);
}
printf("accept client_fd:%dn",client_fd);
//创建线程为了给客户端发送数据
pthread_t ntid;
int ret = pthread_create(&ntid,NULL,server_send,(void *)&client_fd);
if(ret == 0)
printf("okn");
/*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/
while((recv(client_fd,recv_buf,MAXDATASIZE+1,0))>0){
printf("server recvived:%sn",recv_buf);
}
close(client_fd);
close(sockfd);
return 0;
}
PS:多台client的测试方式,可以在PC上使用Python实现client来验证比较方便,我这边是用两台PC+一块linux开发板来测试的。
使用select接口实现的多台client与一台server之间的通讯
select接口的实现不会造成堵塞,但是select的fd_set容量有限,当fd_set过大时系统开销较大。
client端代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h> // for close
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SERVPORT 3333
#define BUFFER_SIZE 1024
#define DUMP_DATA
#ifdef DUMP_DATA
static void dump_data(char* name,char* buf,int len);
#endif
static void show_socket_info(int listen_fd);
static void show_peer_info(int listen_fd);
static int prepare_connect(struct hostent *host);
static void fd_set_init(fd_set* client_fd_set,int* max_fd,int client_fd);
static void send_stdin_to_server(fd_set* client_fd_set,int client_sockfd);
static void get_server_msg(fd_set* client_fd_set,int client_sockfd);
static void check_event(fd_set* client_fd_set,int* max_fd,int client_fd);
char recv_msg[BUFFER_SIZE];
char input_msg[BUFFER_SIZE];
int main(int argc,char *argv[])
{
int client_sockfd = -1;
//char buf_num[6] = {0,};
char str[6] = {0,};
//int num = 0;
struct hostent *host;
if(argc < 2){
fprintf(stderr,"Please enter the server's hostname!n");
exit(1);
}
/*地址解析函数*/
if((host=gethostbyname(argv[1]))==NULL){
perror("gethostbyname");
exit(1);
}
strcpy(str,argv[2]); //第二个参数为client名称
int fd = 0;
fd = open("/dev/mem", O_RDWR | O_NDELAY);
client_sockfd = prepare_connect(host);
if(client_sockfd < 0){
perror("prepare_connect");
exit(0);
}
fd_set client_fd_set;
int max_fd = -1;
while(1){
fd_set_init(&client_fd_set,&max_fd,client_sockfd);
check_event(&client_fd_set,&max_fd,client_sockfd);
}
return 0;
}
#ifdef DUMP_DATA
static void dump_data(char* name,char* buf,int len)
{
int i;
printf("buf:");
for(i=0; i<len; i++){
if(i%8 == 0)
printf("n");
printf("%4d ",buf[i]);
}
printf("n");
}
#endif
static int prepare_connect(struct hostent *host)
{
struct sockaddr_in serv_addr;
int client_sockfd = -1;
char **pptr;
/*创建socket*/
if((client_sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))== -1){
perror("socket");
exit(1);
}
/*设置sockaddr_in 结构体中相关参数*/
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERVPORT);
serv_addr.sin_addr=*((struct in_addr *)host->h_addr);
bzero(&(serv_addr.sin_zero),8);
/*调用connect函数主动发起对服务器端的连接*/
if(connect(client_sockfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr))== -1){
perror("connect");
return -1;
}
show_socket_info(client_sockfd);
//show_peer_info(client_sockfd);
printf("official hostname:%sn", host->h_name); //主机规范名
for(pptr = host->h_aliases; *pptr != NULL; pptr++) //将主机别名打印出来
printf("alias: %sn", *pptr);
printf("client_sockfd:%d n",client_sockfd);
return client_sockfd;
}
static void fd_set_init(fd_set* client_fd_set,int* max_fd,int client_fd)
{
FD_ZERO(client_fd_set);
FD_SET(STDIN_FILENO, client_fd_set);
if(*max_fd < STDIN_FILENO){
*max_fd = STDIN_FILENO;
}
FD_SET(client_fd, client_fd_set);
if(*max_fd < client_fd){
*max_fd = client_fd;
}
printf("max_fd:%d client_fd:%dn",*max_fd,client_fd);
//dump_data("fd",(char*)client_fd_set,128);
}
static void send_stdin_to_server(fd_set* client_fd_set,int client_sockfd)
{
//将键盘输入消息发送给socket
if(FD_ISSET(STDIN_FILENO, client_fd_set)){
bzero(input_msg, BUFFER_SIZE);
ssize_t size = read(STDIN_FILENO, input_msg, sizeof(input_msg));
printf("client send:%s n",input_msg);
printf("client_sockfd:%dn",client_sockfd);
if(send(client_sockfd, input_msg, size, 0) == -1){
perror("send error!n");
}
}
}
static void get_server_msg(fd_set* client_fd_set,int client_sockfd)
{
if(FD_ISSET(client_sockfd, client_fd_set)){
bzero(recv_msg, BUFFER_SIZE);
int byte_num = recv(client_sockfd, recv_msg, BUFFER_SIZE, 0);
if(byte_num > 0){
if(byte_num > BUFFER_SIZE){
byte_num = BUFFER_SIZE;
recv_msg[byte_num] = '';
}
printf("client:%d receive msg:%sn",client_sockfd,recv_msg);
}
else if(byte_num < 0){
printf("receive error!n");
}
else{
printf("server is quitted!n");
exit(0);
}
}
}
static void check_event(fd_set* client_fd_set,int* max_fd,int client_fd)
{
struct timeval tv;
tv.tv_sec = 20;
tv.tv_usec = 0;
int ret = select(*max_fd + 1, client_fd_set, NULL, NULL, &tv);
if(ret < 0){
perror("select errn");
return;
}
else if(ret == 0){
printf("select tmon");
return;
}
else{
//将键盘输入消息发送给socket
send_stdin_to_server(client_fd_set,client_fd);
get_server_msg(client_fd_set,client_fd);
}
}
static void show_socket_info(int listen_fd)
{
struct sockaddr_in *tmp_sockaddr;
struct sockaddr connectedAddr;
int ret = -1;
socklen_t connectedAddrLen = sizeof(connectedAddr);
//获取listen_fd表示的连接上的本地地址
ret = getsockname(listen_fd, (struct sockaddr *)&connectedAddr, &connectedAddrLen);
if(ret){
perror("getsockname");
return;
}
tmp_sockaddr = (struct sockaddr_in *)&connectedAddr;
printf("socket address = %s:%dn", inet_ntoa(tmp_sockaddr->sin_addr), ntohs(tmp_sockaddr->sin_port));
}
static void show_peer_info(int listen_fd)
{
struct sockaddr_in peerAddr;
char ipAddr[INET_ADDRSTRLEN];//保存点分十进制的地址
socklen_t peerLen;
int ret = -1;
//获取listen_fd表示的连接上的对端地址
ret = getpeername(listen_fd, (struct sockaddr *)&peerAddr, &peerLen);
if(ret){
perror("getpeername");
return;
}
printf("socket peer address = %s:%dn", inet_ntop(AF_INET,&(peerAddr.sin_addr), ipAddr, sizeof(ipAddr)),
ntohs(peerAddr.sin_port));
}
server端代码如下:
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/select.h>
#define SERVPORT 3333
#define BACKLOG 10
#define MAX_CONNECTED_NO 10
#define BUFFER_SIZE 1024
#define QUIT_CMD ".quit"
int client_fds[MAX_CONNECTED_NO] = {0,};
char input_msg[BUFFER_SIZE];
char recv_msg[BUFFER_SIZE];
#define DUMP_DATA
#ifdef DUMP_DATA
static void dump_data(char* name,char* buf,int len);
#endif
static int socket_bind(int port);
static void fd_set_init(fd_set* serv_fd_set,int* max_fd,int listen_fd);
static void show_socket_info(int listen_fd);
static void show_peer_info(int listen_fd);
static void update_fd_set(fd_set* serv_fd_set,int* max_fd);
static void broadcast_stdin(fd_set* serv_fd_set);
static void handle_new_connect(fd_set* serv_fd_set,int listen_fd);
static void forward_from_recv(fd_set* serv_fd_set);
static void check_event(fd_set* serv_fd_set,int* max_fd,int listen_fd);
int main()
{
int listen_fd = 0;
listen_fd = socket_bind(SERVPORT);
/*调用listen函数*/
if(listen(listen_fd,BACKLOG)== -1){
perror("listen");
exit(1);
}
printf("listening....n");
//fd_set
fd_set server_fd_set;
int max_fd = -1;
while(1){
fd_set_init(&server_fd_set,&max_fd,listen_fd);
update_fd_set(&server_fd_set,&max_fd);
check_event(&server_fd_set,&max_fd,listen_fd);
}
return 0;
}
static int socket_bind(int port)
{
struct sockaddr_in server_sockaddr;
int listen_fd;
/*建立socket连接*/
if((listen_fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))== -1){
perror("socket");
exit(1);
}
printf("socket success!,listen_fd=%dn",listen_fd);
/*设置sockaddr_in 结构体中相关参数*/
server_sockaddr.sin_family=AF_INET;
server_sockaddr.sin_port=htons(port);
server_sockaddr.sin_addr.s_addr=INADDR_ANY;
bzero(&(server_sockaddr.sin_zero),8);
/*绑定函数bind*/
if(bind(listen_fd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))== -1){
perror("bind");
exit(1);
}
printf("bind success!n");
show_socket_info(listen_fd);
return listen_fd;
}
static void fd_set_init(fd_set* serv_fd_set,int* max_fd,int listen_fd)
{
FD_ZERO(serv_fd_set);
FD_SET(STDIN_FILENO, serv_fd_set);
if(*max_fd < STDIN_FILENO){
*max_fd = STDIN_FILENO;
}
FD_SET(listen_fd, serv_fd_set);
if(*max_fd < listen_fd){
*max_fd = listen_fd;
}
//printf("max_fd=%dn",*max_fd);
//dump_data("fd",(char*)serv_fd_set,128);
}
static void update_fd_set(fd_set* serv_fd_set,int* max_fd)
{
int i;
for(i = 0; i < MAX_CONNECTED_NO; i++){
//printf("client_fds[%d]=%dn", i, client_fds[i]);
if(client_fds[i] != 0){
FD_SET(client_fds[i], serv_fd_set);
if(*max_fd < client_fds[i]){
*max_fd = client_fds[i];
printf("update max_fd=%dn",*max_fd);
}
}
}
}
static void handle_new_connect(fd_set* serv_fd_set,int listen_fd)
{
int i;
if(FD_ISSET(listen_fd, serv_fd_set)){
//有新的连接请求
struct sockaddr_in client_address = {0};
socklen_t address_len = sizeof(client_address);
int client_sock_fd = accept(listen_fd, (struct sockaddr *)&client_address, &address_len);
printf("new connection client_sock_fd = %dn", client_sock_fd);
if(client_sock_fd > 0){
int index = -1;
for(i = 0; i < MAX_CONNECTED_NO; i++){
if(client_fds[i] == 0){
index = i;
client_fds[i] = client_sock_fd;
break;
}
}
if(index >= 0){
printf("new client(%d)add success,%s:%dn",index,
inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
show_socket_info(client_sock_fd);
}
else{
bzero(input_msg, BUFFER_SIZE);
strcpy(input_msg,"client amount is the maxium,can't add!n");
send(client_sock_fd, input_msg, BUFFER_SIZE, 0);
printf("client amount is the maxium,new client joined fail %s:%dn",
inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
}
}
}
}
static void broadcast_stdin(fd_set* serv_fd_set)
{
int i;
if(FD_ISSET(STDIN_FILENO, serv_fd_set)){
bzero(input_msg, BUFFER_SIZE);
ssize_t size = read(STDIN_FILENO, input_msg, sizeof(input_msg));
printf("stdinput msg:%sn",input_msg);
//输入“.quit"则退出服务器
if(strcmp(input_msg, QUIT_CMD) == 0){
exit(0);
}
for(i = 0; i < MAX_CONNECTED_NO; i++){
if(client_fds[i] != 0){
printf("send to client_fds[%d]=%dn", i, client_fds[i]);
if(send(client_fds[i], input_msg, size, 0) == -1){
perror("send error!n");
}else
printf("send ok n");
}
}
}
}
static void forward_from_recv(fd_set* serv_fd_set)
{
int i;
for(i = 0; i < MAX_CONNECTED_NO; i++){
if(client_fds[i] !=0){
if(FD_ISSET(client_fds[i], serv_fd_set)){
//处理某个客户端过来的消息
bzero(recv_msg, BUFFER_SIZE);
int byte_num = recv(client_fds[i], recv_msg, BUFFER_SIZE, 0);
if (byte_num > 0){
if(byte_num > BUFFER_SIZE){
byte_num = BUFFER_SIZE;
recv_msg[byte_num] = '';
}
printf("recv from client(%d),msg:%s n",i,recv_msg);
/*转发数据给其他的客户端*/
for(int i = 0; i < MAX_CONNECTED_NO; i++){
if(client_fds[i] != 0){
send(client_fds[i], recv_msg, sizeof(recv_msg), 0);
}
}
}
else if(byte_num < 0){
printf("get msg error from client(%d).n", i);
}
else{
FD_CLR(client_fds[i], serv_fd_set);
client_fds[i] = 0;
printf("client(%d) is quittedn", i);
}
}
}
}
}
static void check_event(fd_set* serv_fd_set,int* max_fd,int listen_fd)
{
struct timeval tv; //超时时间设置
int ret = -1;
tv.tv_sec = 20;
tv.tv_usec = 0;
//dump_data("fd check",(char*)serv_fd_set,128);
//printf("max_fd:%dn",*max_fd);
ret = select(*max_fd + 1, serv_fd_set, NULL, NULL, &tv);
if(ret < 0){
perror("select errorn");
return;
}
else if(ret == 0){
printf("select tmon");
return;
}
else{
//ret 为未状态发生变化的文件描述符的个数
printf("ret:%d n",ret);
handle_new_connect(serv_fd_set,listen_fd);
broadcast_stdin(serv_fd_set);
forward_from_recv(serv_fd_set);
}
}
#ifdef DUMP_DATA
static void dump_data(char* name,char* buf,int len)
{
int i;
printf("%s:",name);
for(i=0; i<len; i++){
if(i%8 == 0)
printf("n");
printf("%4d ",buf[i]);
}
printf("n");
}
#endif
static void show_socket_info(int listen_fd)
{
struct sockaddr_in *tmp_sockaddr;
struct sockaddr connectedAddr;
int ret = -1;
socklen_t connectedAddrLen = sizeof(connectedAddr);
//获取listen_fd表示的连接上的本地地址
ret = getsockname(listen_fd, (struct sockaddr *)&connectedAddr, &connectedAddrLen);
if(ret){
perror("getsockname");
return;
}
tmp_sockaddr = (struct sockaddr_in *)&connectedAddr;
printf("socket address = %s:%dn", inet_ntoa(tmp_sockaddr->sin_addr), ntohs(tmp_sockaddr->sin_port));
}
static void show_peer_info(int listen_fd)
{
struct sockaddr_in peerAddr;
char ipAddr[INET_ADDRSTRLEN];//保存点分十进制的地址
socklen_t peerLen;
int ret = -1;
//获取listen_fd表示的连接上的对端地址
ret = getpeername(listen_fd, (struct sockaddr *)&peerAddr, &peerLen);
if(ret){
perror("getpeername");
return;
}
printf("socket peer address = %s:%dn", inet_ntop(AF_INET,&(peerAddr.sin_addr), ipAddr, sizeof(ipAddr)),
ntohs(peerAddr.sin_port));
}
使用epoll接口实现的多台client与一台server之间的通讯
epoll的性能较select更优,结合线程池方式能够处理海量并发连线事件。但epoll对应的接口都抽象为统一的读、写等等统一接口,各个epoll对象之间的状态改变比较晦涩,需要有透彻的理解才可以。
客户端代码:
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define MAXSIZE 1024
#define SERVPORT 4444
#define FDSIZE 1024
#define EPOLLEVENTS 20
static void handle_connection(int sockfd);
static void
handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf);
static void do_read(int epollfd,int fd,int sockfd,char *buf);
static void do_write(int epollfd,int fd,int sockfd,char *buf);
static void add_event(int epollfd,int fd,int state);
static void delete_event(int epollfd,int fd,int state);
static void modify_event(int epollfd,int fd,int state);
static void setnonblocking(int sock)
{
int opts;
opts=fcntl(sock,F_GETFL);
if(opts<0){
perror("fcntl(sock,GETFL)");
return;
}
opts = opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<0){
perror("fcntl(sock,SETFL,opts)");
return;
}
}
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in servaddr;
struct hostent *host;
struct sockaddr_in clientAddr;//客户端地址
char ipAddress[INET_ADDRSTRLEN];//保存点分十进制的ip地址
socklen_t clientAddrLen = sizeof(clientAddr);
char **pptr;
char str[6] = {0,};
if(argc < 2){
fprintf(stderr,"Please enter the server's hostname!n");
exit(1);
}
/*地址解析函数*/
if((host=gethostbyname(argv[1]))==NULL){
perror("gethostbyname");
exit(1);
}
strcpy(str,argv[2]); //第二个参数为client名称
sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
/*设置sockaddr_in 结构体中相关参数*/
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(SERVPORT);
servaddr.sin_addr=*((struct in_addr *)host->h_addr);
bzero(&(servaddr.sin_zero),8);
/*调用connect函数主动发起对服务器端的连接*/
if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr))== -1){
perror("connect");
exit(1);
}
//setnonblocking(sockfd);
getsockname(sockfd, (struct sockaddr*)&clientAddr, &clientAddrLen);//获取sockfd表示的连接上的本地地址
printf("client address = %s:%dn", inet_ntop(AF_INET, &clientAddr.sin_addr, ipAddress, sizeof(ipAddress)),
ntohs(clientAddr.sin_port));
printf("official hostname:%sn", host->h_name); //主机规范名
for(pptr = host->h_aliases; *pptr != NULL; pptr++) //将主机别名打印出来
printf("alias: %sn", *pptr);
printf("client sockfd:%d n",sockfd);
//处理连接
//while(1)
sleep(1);
handle_connection(sockfd);
close(sockfd);
return 0;
}
static void handle_connection(int sockfd)
{
int epollfd;
struct epoll_event events[EPOLLEVENTS];
char buf[MAXSIZE];
int ret;
epollfd = epoll_create(FDSIZE);
add_event(epollfd,STDIN_FILENO,EPOLLIN);
add_event(epollfd,sockfd,EPOLLIN);
for ( ; ; )
{
ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1);
handle_events(epollfd,events,ret,sockfd,buf);
}
close(epollfd);
}
static void
handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf)
{
int fd;
int i;
//printf("sockfd=%d n",sockfd);
for (i = 0;i < num;i++)
{
fd = events[i].data.fd;
//printf("fd=%d n",fd);
if (events[i].events & EPOLLIN)
do_read(epollfd,fd,sockfd,buf);
else if (events[i].events & EPOLLOUT)
do_write(epollfd,fd,sockfd,buf);
}
}
static void do_read(int epollfd,int fd,int sockfd,char *buf)
{
int nread;
nread = read(fd,buf,MAXSIZE);
if (nread == -1)
{
perror("read error:");
close(fd);
}
else if (nread == 0)
{
fprintf(stderr,"server close.n");
close(fd);
}
else
{
if (fd == STDIN_FILENO){
printf("client send to %d n",sockfd);
modify_event(epollfd,sockfd,EPOLLOUT);
}
else{
printf("read from server n");
//modify_event(epollfd,sockfd,EPOLLIN);
add_event(epollfd,STDOUT_FILENO,EPOLLOUT);
}
}
}
static void do_write(int epollfd,int fd,int sockfd,char *buf)
{
int nwrite;
printf("client writr to %d,str:%s n",fd,buf);
nwrite = write(fd,buf,strlen(buf));
if (nwrite == -1)
{
perror("write error:");
close(fd);
}
else
{
if (fd == STDOUT_FILENO)
delete_event(epollfd,fd,EPOLLOUT);
else
modify_event(epollfd,fd,EPOLLIN);
}
memset(buf,0,MAXSIZE);
}
static void add_event(int epollfd,int fd,int state)
{
struct epoll_event ev;
ev.events = state|EPOLLET;
ev.data.fd = fd;
printf("EPOLL_CTL_ADD fd:%d state:%dn",fd,state);
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);
}
static void delete_event(int epollfd,int fd,int state)
{
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
printf("EPOLL_CTL_DEL fd:%d state:%dn",fd,state);
epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev);
}
static void modify_event(int epollfd,int fd,int state)
{
struct epoll_event ev;
ev.events = state|EPOLLET;
ev.data.fd = fd;
printf("EPOLL_CTL_MOD fd:%d state:%dn",fd,state);
epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev);
}
解读一下client端,do_read/do_write函数中的几个epoll接口的引用:
do_read
if (fd == STDIN_FILENO){
printf("client send to %d n",sockfd);
modify_event(epollfd,sockfd,EPOLLOUT);
}
else{
printf("read from server n");
add_event(epollfd,STDOUT_FILENO,EPOLLOUT);
}
do_write
if (fd == STDOUT_FILENO)
delete_event(epollfd,fd,EPOLLOUT);
else
modify_event(epollfd,fd,EPOLLIN);
- do_read,当监控到stdin输入时,将sockfd变为EPOLLOUT(输出)状态,以执行将键盘输入发送给server端。
- 此后会触发do_write,在将数据发送给server后,将fd的状态改回EPOLLIN(监控输入)状态。
- do_read,当监控到server socket发送过来的数据时,需要增加一个STDOUT_FILENO的标准输出(EPOLLOUT),来讲数据显示到屏幕上。
- 此后会触发do_write,将数据写到屏幕上后,再删除该标准输出STDOUT_FILENO的epoll。
总结来看:
- 当一个fd的epoll已经被add过,再执行add(EPOLL_CTL_ADD)操作 ,此后并不会触发add指定的读/写操作,而应该使用modify(EPOLL_CTL_MOD)。
- 同理,某些fd的epoll也可以采用先delete(EPOLL_CTL_DEL),再add(EPOLL_CTL_ADD)操作也可以。
服务端代码,服务端引用了网络上线程池的代码,待深入了解。
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#define MAXLINE 10
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 4444
#define INFTIM 1000
//线程池任务队列结构体
struct task{
int fd; //需要读写的文件描述符
struct task *next; //下一个任务
};
//用于读写两个的两个方面传递参数
struct user_data{
int fd;
unsigned int n_size;
char line[MAXLINE];
};
//线程的任务函数
void * readtask(void *args);
void * writetask(void *args);
//声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
struct epoll_event ev,events[20];
int epfd;
pthread_mutex_t mutex;
pthread_cond_t cond1;
struct task *readhead=NULL,*readtail=NULL,*writehead=NULL;
static void setnonblocking(int sock)
{
int opts;
opts=fcntl(sock,F_GETFL);
if(opts<0){
perror("fcntl(sock,GETFL)");
return;
}
opts = opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<0){
perror("fcntl(sock,SETFL,opts)");
return;
}
}
int main()
{
int i, maxi, listenfd, connfd, sockfd,nfds;
pthread_t tid1,tid2;
struct task *new_task=NULL;
//struct user_data *rdata=NULL;
socklen_t clilen;
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond1,NULL);
//初始化用于读线程池的线程
pthread_create(&tid1,NULL,readtask,NULL);
pthread_create(&tid2,NULL,readtask,NULL);
listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//生成用于处理accept的epoll专用的文件描述符
epfd=epoll_create(256);
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
//把socket设置为非阻塞方式
//setnonblocking(listenfd);
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port=htons(SERV_PORT);
serveraddr.sin_addr.s_addr = INADDR_ANY;
bind(listenfd,(struct sockaddr *)&serveraddr, sizeof(serveraddr));
listen(listenfd, LISTENQ);
printf("server listenfd=%d n",listenfd);
//处理连接
//while(1)
sleep(1);
maxi = 0;
for ( ; ; ) {
//等待epoll事件的发生
nfds = epoll_wait(epfd,events,20,500);
//printf("trigered event num=%d n",nfds);
//处理所发生的所有事件
for(i=0;i<nfds;++i){
if(events[i].data.fd==listenfd){
connfd = accept(listenfd,(struct sockaddr *)&clientaddr, &clilen);
if(connfd<0){
perror("connfd<0");
return 0;
}
setnonblocking(connfd);
printf("new connection connfd = %dn", connfd);
printf("new client add success,%s:%dn",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
//设置用于读操作的文件描述符
ev.data.fd=connfd;
//设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET;
//注册ev
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
}
else if(events[i].events&EPOLLIN){
printf("reading event[%d].data.fd=%d/n",i,events[i].data.fd);
if((sockfd = events[i].data.fd) < 0)
continue;
new_task = malloc(sizeof(struct task));
//new_task=new task();
new_task->fd=sockfd;
new_task->next=NULL;
//添加新的读任务
pthread_mutex_lock(&mutex);
if(readhead==NULL){
readhead=new_task;
readtail=new_task;
}
else{
readtail->next=new_task;
readtail=new_task;
}
//唤醒所有等待cond1条件的线程
pthread_cond_broadcast(&cond1);
pthread_mutex_unlock(&mutex);
}
else if(events[i].events&EPOLLOUT){
printf("writing event[%d].data.fd=%d/n",i,events[i].data.fd);
/*
rdata=(struct user_data *)events[i].data.ptr;
sockfd = rdata->fd;
write(sockfd, rdata->line, rdata->n_size);
delete rdata;
//设置用于读操作的文件描述符
ev.data.fd=sockfd;
//设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET;
//修改sockfd上要处理的事件为EPOLIN
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
*/
}
}
}
}
static int count111 = 0;
static time_t oldtime = 0, nowtime = 0;
void * readtask(void *args)
{
int fd=-1;
unsigned int n = 0;
//用于把读出来的数据传递出去
struct user_data *data = NULL;
while(1){
pthread_mutex_lock(&mutex);
//等待到任务队列不为空
while(readhead==NULL)
pthread_cond_wait(&cond1,&mutex);
fd=readhead->fd;
//从任务队列取出一个读任务
struct task *tmp=readhead;
readhead = readhead->next;
//delete tmp;
free(tmp);
tmp = NULL;
pthread_mutex_unlock(&mutex);
//data = new user_data();
data = malloc(sizeof(struct user_data));
data->fd=fd;
char recvBuf[1024] = {0};
int ret = 999;
int rs = 1;
while(rs){
ret = recv(fd,recvBuf,1024,0);// 接受客户端消息
if(ret < 0){
//由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可//读在这里就当作是该次事件已处理过。
if(errno == EAGAIN){
printf("EAGAINn");
break;
}
else{
printf("recv error!n");
close(fd);
break;
}
}
else if(ret == 0){
// 这里表示对端的socket已正常关闭.
rs = 0;
}
if(ret == sizeof(recvBuf))
rs = 1; // 需要再次读取
else
rs = 0;
}
if(ret>0){
//-------------------------------------------------------------------------------
data->n_size=n;
count111 ++;
struct tm *today;
time_t ltime;
time( &nowtime );
if(nowtime != oldtime){
printf("%dn", count111);
oldtime = nowtime;
count111 = 0;
}
char buf[1000] = {0};
sprintf(buf,"HTTP/1.0 200 OKrnContent-type: text/plainrnrn%s","Hello world!n");
send(fd,buf,strlen(buf),0);
//close(fd);
}
}
}