linux网络编程聊天室

照着书上写了一个C/S模式的聊天室,通过查看函数和宏源码注释加了点自己的注释
客户端:

#define _GNU_SOURCE 1
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<poll.h>
#include<fcntl.h>

#define BUFFER_SIZE 64

int main(int argc,char* argv[]){
    if(argc<=2){
        printf("usage: %s ip_address port_number\n",basename(argv[0]));
        return 1;
    }
    const char* ip=argv[1];
    int port=atoi(argv[2]);

    //创建服务地址并初始化
    struct sockaddr_in server_address;
    // 置字节字符串前n个字节为零且包括‘\0’。
    bzero(&server_address,sizeof(server_address));
    //指定ipv4协议
    server_address.sin_family=AF_INET;
    inet_pton(AF_INET,ip,&server_address.sin_addr);
    server_address.sin_port=htons(port);

    int sockfd=socket(PF_INET,SOCK_STREAM,0);
    assert(sockfd >= 0);
    if(connect(sockfd,(struct sockaddr*)&server_address,sizeof(server_address))<0){
        printf("connection failed\n");
        close(sockfd);
        return 1;
    }
    pollfd fds[2];
    // 注册文件描述符0(标准输入)和文件描述符sockfd上的可读事件
    fds[0].fd=0;
    fds[0].events=POLLIN;
    fds[0].revents=0;
    fds[1].fd=sockfd;
    fds[1].events=POLLIN | POLLRDHUP;
    fds[1].revents=0;

    char read_buf[BUFFER_SIZE];
    int pipefd[2];
    int ret=pipe(pipefd);
    assert(ret != -1);

    while (1)
    {
       ret=poll(fds,2,-1);
       if(ret<0){
           printf("poll failure");
           break;
       }
       if(fds[1].revents & POLLRDHUP){
           printf("server close the connection\n");
           break;
       }
       else if(fds[1].revents & POLLIN){
           memset(read_buf, '\0',BUFFER_SIZE);
           recv(fds[1].fd,read_buf,BUFFER_SIZE-1,0);
           printf("%s\n",read_buf);
       }
       if(fds[0].revents & POLLIN){
        //    使用splice将用户输入的数据直接写到sockfd上(零拷贝)
        ret=splice(0,NULL,pipefd[1],NULL,32768,SPLICE_F_MORE | SPLICE_F_MOVE);
        ret=splice(pipefd[0],NULL,sockfd,NULL,32768,SPLICE_F_MORE | SPLICE_F_MOVE);

       }
    }
    close(sockfd);
    return 0;

}

服务器:

#define _GNU_SOURCE 1
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<poll.h>
#include<fcntl.h>
#include<errno.h>

#define USER_LIMIT 5    //最大用户数量
#define BUFFER_SIZE 64  //读缓冲区的大小
#define FD_LIMIT 65535  //文件描述符数量限制
// 客户数据:客户端socket地址、待写到客户端的数据的位置、从客户端读入的数据
struct client_data
{
    sockaddr_in address;
    char* write_buf;
    char buf[BUFFER_SIZE];
};

int setnonblocking(int fd){
    // 获取文件状态标志
    int old_option=fcntl(fd,F_GETFL);
    int new_option=old_option | O_NONBLOCK;
    fcntl(fd,F_SETFL,new_option);
    return old_option;
}

int main(int argc,char* argv[]){
    if(argc <= 2){
        printf("usage: %s ip_address port_number\n",basename(argv[0]));
        return 1;
    }
    const char* ip=argv[1];
    int port=atoi(argv[2]);

    int ret=0;
    struct sockaddr_in address;
    // 将指定内存块的前n个字节全部设置为零。
    bzero(&address,sizeof(address));
    address.sin_family=AF_INET;
    inet_pton(AF_INET,ip,&address.sin_addr);
    address.sin_port=htons(port);

    int listenfd=socket(PF_INET,SOCK_STREAM,0);
    assert(listenfd >=0);
    
    ret=bind(listenfd,(struct sockaddr*)&address,sizeof(address));
    assert(ret!=-1);

    ret=listen(listenfd,5);
    assert(ret!=-1);

    // 创建一个user数组,分配FD_LIMIE个client_data对象。可以预期:每个可能的socket连接都可以获得一个这样的对象
    // 并且socket的值可以直接用来索引(作为数组的下标)socket连接对应的client_data对象,
    // 这是将socket和客户数据关联的简单而高效的方式
    client_data* users=new client_data[FD_LIMIT];
    // 尽管我们分配了足够多的client_data对象,但为了提高poll性能,仍然有必要限制用户的数量
    pollfd fds[USER_LIMIT+1];   //描述轮询请求的数据结构
    int user_counter=0;
    for (int i = 1; i < USER_LIMIT; ++i)
    {
        fds[i].fd= -1;
        fds[i].events=0;

    }
    fds[0].fd=listenfd;
    fds[0].events=POLLIN | POLLERR; //有数据需要读取   错误条件
    fds[0].revents=0;
    
    while (1)
    {
       ret=poll(fds,user_counter+1,-1);
       if(ret<0){
           printf("poll failure\n");
           break;
       }
       for (int i = 0; i < user_counter+1; ++i)
       {
           if((fds[i].fd==listenfd) && (fds[i].revents & POLLIN)){
            //    描述Internet套接字地址的结构
               struct sockaddr_in client_address;
               socklen_t client_addrlength=sizeof(client_address);
               int connfd=accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
               if(connfd<0){
                    printf("errno is:%d\n",errno);
                    continue;
               }
            //    如果请求太多,则关闭新到的连接
                if(user_counter >= USER_LIMIT){
                    const char* info ="too many users\n";
                    printf("%s",info);
                    send(connfd,info,strlen(info),0);
                    close(connfd);
                    continue;
                }
                // 对于新的连接,同时修改fds和users数组。前文已经提到,users[connfd]
                // 对应于新连接文件描述符connnfd的客户数据
                user_counter++;
                users[connfd].address=client_address;
                setnonblocking(connfd);
                fds[user_counter].events=POLLIN | POLLRDHUP | POLLERR; 
                fds[user_counter].revents=0;
                printf("comes a new user,now have %d users\n",user_counter);
           }
           else if (fds[i].revents & POLLERR)
           {
               printf("get an error from %d\n",fds[i].fd);
               char errors[100];
               memset(errors,'\0',100);
               socklen_t length=sizeof(errors);
               if (getsockopt(fds[i].fd,SOL_SOCKET,SO_ERROR,&errors,&length)<0)
               {
                   printf("get socket option failed\n");
               }
               continue;
               
           }
           else if(fds[i].revents & POLLHUP)
           {
            //如果客户端关闭连接,则服务器也关闭对应的链接,并将用户总数减1
                users[fds[i].fd]=users[fds[user_counter].fd];
                close(fds[i].fd);
                fds[i]=fds[user_counter];
                i--;
                user_counter--;
                printf("a client left\n");
           }
           else if(fds[i].revents & POLLIN)
           {
               int connfd=fds[i].fd;
               memset(users[connfd].buf,'\0',BUFFER_SIZE);
            //    (1)第一个参数指定接收端套接字描述符;
            //    (2)第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
            //     (3)第三个参数指明buf的长度;(4)第四个参数一般置0。
                ret=recv(connfd,users[connfd].buf,BUFFER_SIZE-1,0);
                printf("get %d bytes of client data %s from %d\n",ret,users[connfd].buf,connfd);
                if(ret<0){
                    // 如果读取错误,则关闭连接
                    if(errno != EAGAIN){
                        close(connfd);
                        users[fds[i].fd]=users[fds[user_counter].fd];
                        fds[i]=fds[user_counter];
                        i--;
                        user_counter--;
                    }
                }
                else if(ret==0)
                {
                }
                else
                {
                    // 如果接收到客户数据,则通知其他socket连接准备写数据
                    for (int j = 1; i < user_counter; ++j)
                    {
                        if (fds[j].fd==connfd)
                        {
                            continue;
                        }
                        fds[j].events |= ~POLLIN;
                        fds[j].events |= POLLIN;
                        users[fds[j].fd].write_buf=users[connfd].buf; 
                    } 
                }   
           }
           else if(fds[i].revents & POLLOUT)
           {
               int connfd=fds[i].fd;
               if(!users[connfd].write_buf){
                   continue;
               }
                ret=send(connfd,users[connfd].write_buf,strlen(users[connfd].write_buf),0);
                users[connfd].write_buf=NULL;
                //    写完数据后需要重新注册fds[i]上的可读事件
                fds[i].events |= ~POLLOUT;
                fds[i].events |= POLLIN;
           }   
       }
    }
    delete[]users;
    close(listenfd);
    return 0;
    
}

编译运行之后,感觉没啥功能,就能检测有多少客户链接,哈哈哈

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑马金牌编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值