代码如下
#include
#include
#include
#include
#include
#include
int main(int argc, char ** argv)
{
if(argc < 2)
{
printf("please input ip and port\n");
exit(1);
}
int serverfd;
serverfd = socket(AF_INET, SOCK_STREAM, 0);
if(serverfd < 0)
{
printf("socket error\n");
exit(1);
}
struct sockaddr_in serv_addr;
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(atoi(argv[2]));
int ret = inet_pton(AF_INET, argv[1], &serv_addr.sin_addr.s_addr);
if(ret < 0)
{
printf("inet_pton error\n");
exit(1);
}
ret = connect(serverfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if(ret < 0 )
{
printf("connect error\n");
exit(1);
}
char buf[1024];
while(1)
{
fgets(buf, sizeof(buf), stdin);
write(serverfd, buf, strlen(buf));
}
close(serverfd);
return 1;
}
代码如下
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXLNE 4096
#define POLLSIZE 1024
int main(int argc, char **argv)
{
int listenfd, connfd, n;
struct sockaddr_in servaddr;
char buff[MAXLNE];
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(atoi(argv[1]));
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
if (listen(listenfd, 10) == -1) {
printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
printf("listen...\n");
#if 0 //单线程
struct sockaddr_in client;
menset(&client, 0, sizeof(client));
socklen_t len = sizeof(client);
if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
printf("========waiting for client's request========\n");
while (1) {
n = recv(connfd, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
send(connfd, buff, n, 0);
} else if (n == 0) {
close(connfd);
}
//close(connfd);
}
实现思路:简单。
代码如下
/*线程*/
void* tfun(void* arg)
{
int connfd = *(int*)arg;
char buff[MAXLNE];
while(1)
{
int n = recv(connfd, buff, MAXLNE, 0);
if (n > 0)
{
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
}
else if (n == 0)
{
close(connfd);
break;
}
}
pthread_exit((void*)1);
}
//main函数中
#elif 0 //多线程
pthread_t tid;
struct sockaddr_in clientsock;
socklen_t len = sizeof(clientsock);
char clie_IP[BUFSIZ];
int i=0;
int thread_connfd[128];
while(1)
{
if((thread_connfd[i] = accept(listenfd, (struct sockaddr*)&clientsock, &len)) != -1)
{
printf("------client ip:%s, port:%d---:", inet_ntop(AF_INET, &clientsock.sin_addr.s_addr,
clie_IP, sizeof(clie_IP)), ntohs(clientsock.sin_port));
pthread_create(&tid, NULL, (void*)tfun, (void*)&thread_connfd[i]);
pthread_detach(tid);
i++;
}
}
缺点:
查看所分配线程的栈的大小:8M。32位Linux下,一个进程空间4G,内核占1G,用户留3G,一个线程默认8M,所以最多380个左右线程。当连接的用户超过一定,内存堆满会导致服务端重启。
select函数原型
int select(int maxfdql, fd_set *readset, fd_set *writeset, fd_set *execeptset, const struct timeval *timeout);
函数功能: 允许进程指示内核等待多个事件中的任何一个发生,并只有一个或者多个事件发生或经历一段指定的事件才唤醒它。
maxfdql:指定待测试的描述符个数,传入的值应该是待测试最大描述符加一。
fd_set类型:实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系。
建立联系的宏
readset:传入传出的可读事件描述符集合。
writeset:传入传出的可写事件描述符集合。
exceptset:传入传出的异常事件描述符集合。
const struct timeval *timeout:告知内核等待所指定描述符的任何一个就绪可以花多长时间,具体看UNP。
有三种可能:
1、永久等下去,阻塞但并不占用cpu。
2、等待一段固定时间。
3、根本不等待,轮询方式。
返回值:监听到事件已经发生的描述符个数。
select服务器实现思路
代码如下
#elif 0 //select实现
fd_set rset, rfds;
FD_ZERO(&rset);
FD_SET(listenfd, &rset);
char clie_IP[BUFSIZ];
int Numready;
int maxfd = listenfd;
while(1)
{
rfds = rset;
Numready = select(maxfd+1, &rfds, NULL, NULL, NULL);
struct sockaddr_in sockclient;
socklen_t len = sizeof(sockclient);
if(FD_ISSET(listenfd, &rfds))
{
if((connfd = accept(listenfd,(struct sockaddr*)&sockclient, &len)) == -1)
{
printf("accept error\n");
exit(1);
}
printf("------connfd:%d,client ip:%s, port:%d---:\n", connfd,inet_ntop(AF_INET, &sockclient.sin_addr.s_addr,
clie_IP, sizeof(clie_IP)), ntohs(sockclient.sin_port));
FD_SET(connfd, &rset);
if(connfd > maxfd)maxfd = connfd;
if(--Numready == 0)continue;
}
for(int i = listenfd; i<=maxfd; i++)
{
if(FD_ISSET(i, &rfds))
{
n = recv(i, buff, sizeof(buff), 0);
if(n>0)
{
buff[n] = '\0';
printf("msg from client[%d]%s\n", i,buff);
}
else if(n == 0)
{
FD_CLR(i, &rfds);
printf("%d disconnected\n", i);
close(i);
}
if(--Numready == 0)break;
}
}
}
poll函数原型
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
struct pollfd{
int fd; //fd to check(还是英文直观)
short events; //对于fd需要检查的事件,输入值
short revents; //检查到的事件,返回值
}
nfds:指定待测试的描述符个数,传入的值应该是待测试最大描述符加一。
timeout:有三种可能:
1、永久等下去,阻塞但并不占用cpu。
2、等待一段固定时间。
3、根本不等待,轮询方式。
poll服务器实现思路
和select类似,不过poll体现在结构体上。
代码如下
#elif 0 //poll实现
struct pollfd pollarrfd[POLLSIZE] = {0};
int maxfd = listenfd;
char clie_IP[BUFSIZ];
pollarrfd[0].fd = listenfd;
pollarrfd[0].events = POLLIN;
struct sockaddr_in sockclient;
socklen_t len = sizeof(sockclient);
while(1)
{
int Numready = poll(pollarrfd, maxfd+1, -1);
if(Numready < 0)
{
printf("poll error\n");
exit(1);
}
if(pollarrfd[0].revents & POLLIN)
{
connfd = accept(listenfd, (struct sockaddr*)&sockclient, &len);
if(connfd < 0)
{
printf("accept error\n");
exit(1);
}
printf("accept\n");
printf("------connfd:%d,client ip:%s, port:%d---:\n", connfd, inet_ntop(AF_INET, &sockclient.sin_addr.s_addr,
clie_IP, sizeof(clie_IP)), ntohs(sockclient.sin_port));
pollarrfd[connfd].fd = connfd;
pollarrfd[connfd].events = POLLIN;
if(connfd > maxfd)maxfd = connfd;
if(--Numready == 0)continue;
}
for(int i = 1; i<=maxfd; i++)
{
if(pollarrfd[i].revents & POLLIN)
{
n = recv(i, buff, sizeof(buff), 0);
if(n<0)
{
printf("recv error\n");
exit(1);
}
if(n>0)
{
buff[n] = '\0';
printf("msg from client[%d]:%s\n", i, buff);
}
if(n==0)
{
close(i);
printf("socket[%d] close\n", i);
}
}
}
}
函数接口
int epoll_create(int size)
size:监听个数。 返回值:epoll句柄。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd:epoll_create创建的epoll句柄。
op:表示动作,用3个宏来表示:
EPOLL_CTL_ADD (注册新的fd到epfd),
EPOLL_CTL_MOD (修改已经注册的fd的监听事件),
EPOLL_CTL_DEL (从epfd删除一个fd);
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;
EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT: 表示对应的文件描述符可以写
EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR: 表示对应的文件描述符发生错误
EPOLLHUP: 表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
epfd:epoll句柄。
events:返回值,数组里储存了所有触发事件的文件描述符和事件类型。
maxevents:通知内核这个events多大。
timeout:等待时间
1、-1:阻塞。
2、0:非阻塞。
3、>0:指定毫秒。
epoll实现思路
代码如下
#else //epoll实现
int Numready;
char clie_IP[BUFSIZ];
struct sockaddr_in sockclient;
socklen_t len = sizeof(sockclient);
int epollfd = epoll_create(1);
if(epollfd < 0 )
{
printf("epoll_create error\n");
exit(1);
}
struct epoll_event event[POLLSIZE];
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = listenfd;
int ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev);
if(ret < 0)
{
printf("epoll_ctl error\n");
exit(1);
}
while(1)
{
Numready = epoll_wait(epollfd, event, POLLSIZE, -1);
if(Numready < 0)
{
printf("epoll_wait error\n");
exit(1);
}
for(int i=0; i<Numready; i++)
{
if(event[i].data.fd == listenfd)
{
connfd = accept(listenfd, (struct sockaddr*)&sockclient, &len);
if(connfd < 0)
{
printf("accept error\n");
exit(1);
}
printf("------connfd:%d,client ip:%s, port:%d---:\n", connfd, inet_ntop(AF_INET, &sockclient.sin_addr.s_addr,
clie_IP, sizeof(clie_IP)), ntohs(sockclient.sin_port));
event[i].data.fd = connfd;
event[i].events = EPOLLIN;
ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, event);
if(ret < 0)
{
printf("epoll_ctl_add error\n");
exit(1);
}
continue;
}
else if(event[i].events & EPOLLIN)
{
int clientfd = event[i].data.fd;
n = recv(clientfd, buff, sizeof(buff), 0);
if(n < 0 )
{
printf("recv error\n");
exit(1);
}
if(n>0)
{
buff[n] = '\0';
printf("msg from client[%d]:%s\n", clientfd, buff);
}
if(n==0)
{
close(clientfd);
printf("client[%d] close\n", clientfd);
}
}
}
}
#endif
多线程:实现方法简单,阻塞IO,但是浪费资源,可用来实现少量客户的会议室服务器。
select:配合fd_set位图及其几个宏使用,可对读、写、异常事件非阻塞保留事件,交给程序员遍历查询。
poll:和select很相像,但是poll使用pollfd结构体传入和传出来完成,而且事件类型比select多,接口也没有select多,可以直接通过结构体来判断。
epoll:接口较多,实现的方式比较多,虽然相对复杂但是性能和实现功能上更好。无须遍历侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。目前epell是linux大规模并发网络程序中的热门首选模型。
全部代码自行git clone https://github.com/qiushii/server.git
具体内容,请参考《UNP》《APUE》。