linux C/S 有用户名的聊天程序 only one cilent —— using socket

服务器端源代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>

#define MAXBUF 1024
/************关于本文档********************************************
*filename: async-server.c
*purpose: 演示网络异步通讯,这是服务器端程序
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2007-01-25 21:22
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Google.com
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*修改、添加注释 by: nickolas 
*********************************************************************/

int main(int argc, char **argv)
{
    int sockfd, new_fd;
    socklen_t len;
    struct sockaddr_in my_addr, their_addr;   //服务器绑定地址,客户端协议地址
    unsigned int myport, lisnum;
    char buf[MAXBUF + 1];
    char usrname[MAXBUF + 1], hostname[MAXBUF + 1];
    int flag = 1 ;
    fd_set rfds;
    struct timeval tv;
    int retval, maxfd = -1;

    printf("请输入您的用户名:");
    bzero(hostname, MAXBUF + 1);
    fgets(hostname, MAXBUF, stdin);

    if (argv[1])
        myport = atoi(argv[1]);
    else
        myport = 7838;

    if (argv[2])
        lisnum = atoi(argv[2]);
    else
        lisnum = 2;

    //创建套接字,指定通信类型,返回套接字描述字
    if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {   //协议簇:ipv4,套接字类型:字节流(使用TCP),协议类型:缺省0
        perror("socket");
        exit(1);
    }
/*
使用sockaddr_in这个结构来设置/获取地址信息。
struct   sockaddr_in {
     short                    sin_family;    
     u_short                sin_port
     struct  in_addr    sin_addr;
     char                      sin_zero[8];
 };
sin_family 指代协议族,在socket编程中只能是AF_INET
sin_port 存储端口号(使用网络字节顺序)
sin_addr 存储IP地址,使用in_addr这个数据结构
    struct in_addr {
        unsigned long s_addr;
    };
    这个数据结构是由于历史原因保留下来的,主要用作与以前的格式兼容。
  s_addr 按照网络字节顺序存储IP地址
sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
*/
    bzero(&my_addr, sizeof(my_addr));  //extern void bzero(void *s, int n);置字节字符串s的前n个字节为零。
    my_addr.sin_family = PF_INET;          //协议簇
    my_addr.sin_port = htons(myport);   //将一个16位数(端口号)从主机字节顺序转换成网络字节顺序。
    if (argv[3])   //按照网络字节顺序存储IP地址
        my_addr.sin_addr.s_addr = inet_addr(argv[3]);
    else
        my_addr.sin_addr.s_addr = INADDR_ANY;//INADDR_ANY 通配符地址告诉系统假如有多个接口,本服务器进程将接受任意一个Internet 接口的连接。

//int bind( int sockfd, const struct sockaddr *myaddr, socket_t   addrlen );给socket分配本地协议地址(ipaddr和port).
    if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
        == -1) {
        perror("bind");
        exit(1);
    }
//int listen(int sockfd, int backlog);表明服务器进程愿意接收客户进程的连接请求,为套接字建立一个连接请求侦听队列。处理可能同时出现的几个连接请求。
    if (listen(sockfd, lisnum) == -1) {
        perror("listen");
        exit(1);
    }

    while (1) {
    /*需要再次接收用户名*/
    flag = 1;
   
        printf("\n----等待新的连接到来开始新一轮聊天……\n");
        len = sizeof(struct sockaddr);  //协议地址长度
//int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);用于服务器进程接收客户进程的连接请求。函数返回值为新生成的用于连接的套接字(四元组形成)。
        if ((new_fd =
             accept(sockfd, (struct sockaddr *) &their_addr,
                    &len)) == -1) {
            perror("accept");
            exit(errno);
        } else
            printf("server: got connection from %s, port %d, socket %d\n",
                   inet_ntoa(their_addr.sin_addr),
                   ntohs(their_addr.sin_port), new_fd);//ntohs 将一个无符号短整形数从网络字节顺序转换为主机字节顺序。

     send(new_fd, hostname, strlen(hostname) - 1, 0);
        /* 开始处理每个新连接上的数据收发 */
        printf("\n准备就绪,可以开始聊天了……直接输入消息回车即可发信息给对方\n");
        while (1) {
            /* 把集合清空 */
            FD_ZERO(&rfds);
            /* 把标准输入句柄0加入到集合中 */
            FD_SET(0, &rfds);
            maxfd = 0;
            /* 把当前连接句柄new_fd加入到集合中 */
            FD_SET(new_fd, &rfds);
            if (new_fd > maxfd)
                maxfd = new_fd;
            /* 设置最大等待时间 */
            tv.tv_sec = 1;
            tv.tv_usec = 0;
            /* 开始等待 */
            retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
            if (retval == -1) {
                printf("将退出,select出错! %s", strerror(errno));
                break;
            } else if (retval == 0) {
                /* printf
                   ("没有任何消息到来,用户也没有按键,继续等待……\n"); */
                continue;
            } else {
                if (FD_ISSET(0, &rfds)) {
                    /* 用户按键了,则读取用户输入的内容发送出去 */
                    bzero(buf, MAXBUF + 1);
//标准输入 char *fgets(char *buf, int n, FILE  *fp);   从文件流读入一行。
                    fgets(buf, MAXBUF, stdin);
//strncasecmp()用来比较参数s1和s2字符串前n个字符,比较时会自动忽略大小写的差异,若参数s1和s2字符串相同则返回0 s1若大于s2则返回大于0的值 s1若小于s2则返回小于0的值
                    if (!strncasecmp(buf, "quit", 4)) {
                        printf("自己请求终止聊天!\n");
                        break;
                    }
//int send ( int sd, const void *msg, size_t  len, int flags );msg:指向用于发送/接收数据的缓冲区的指针。len: 是缓冲区的字节数。
                    len = send(new_fd, buf, strlen(buf) - 1, 0);
//            hostname[len]='\0';
//            slen = strlen(hostname);
//            hostname[slen-1] = '\0';
                    if (len > 0)
                        printf
                            ("%s的消息: \'%s\'发送成功,共发送了%d个字节!\n",
                             hostname, buf, len);
                    else {
                        printf
                            ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
                             buf, errno, strerror(errno));
                        break;
                    }
                }
                if (FD_ISSET(new_fd, &rfds)) {
            if(flag == 1) {
             /*接收客户端用户名*/
            bzero(usrname, MAXBUF + 1);
            recv(new_fd, usrname, MAXBUF, 0);
            flag = 0;
            }
            else {
                    /* 当前连接的socket上有消息到来则接收对方发过来的消息并显示 */
                    bzero(buf, MAXBUF + 1);
                    /* 接收客户端的消息 */
//int recv  ( int sd,  void *msg, size_t  len, int flags );
                    len = recv(new_fd, buf, MAXBUF, 0);
                    if (len > 0)
                        printf
                            ("收到来自%s的消息:'%s',共%d个字节的数据\n",
                             usrname, buf, len);
                    else {
                        if (len < 0)
                            printf
                                ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
                                 errno, strerror(errno));
                        else
                            printf("对方退出了,聊天终止\n");
                        break;
                    }
            }
                }
            }
        }
        close(new_fd);
        /* 处理每个新连接上的数据收发结束 */
        printf("还要和其它连接聊天吗?(no->退出)");
        fflush(stdout);//清除文件缓冲区,文件以写方式打开时将缓冲区内容写入文件.原型:int fflush(FILE *stream)
        bzero(buf, MAXBUF + 1);
        fgets(buf, MAXBUF, stdin);
        if (!strncasecmp(buf, "no", 2)) {
        flag = 0;
            printf("终止聊天!\n");
            break;
        }
    }

    close(sockfd);//关闭套接字, 发送队列中的余留数据, 终止TCP连接。
    return 0;
}
客户端源代码如下:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>

#define MAXBUF 1024
//int select(int maxfd, fd_set *readfds, fd_set *writefds, fe_set *exceptfds, const struct timeval *timeout);
/*select的第一个参数是文件描述符集中要被检测的比特数,这个值必须至少比待检测的最大文件描述符大1;
参数readfds指定了被读监控的文件描述符集;
参数 writefds指定了被写监控的文件描述符集;
参数exceptfds指定了被例外条件监控的文件描述符集。
参数timeout起了定时器的作用:到了指定的时间,无论是否有设备准备好,都返回调用。*/
/************关于本文档********************************************
// *filename: ssync-client.c
*purpose: 演示网络异步通讯,这是客户端程序
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2007-01-25 21:32
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Google.com
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*修改、添加注释 by: nickolas 
*********************************************************************/
int main(int argc, char **argv)
{
    int sockfd, len;
    struct sockaddr_in dest;
    char buffer[MAXBUF + 1];
    fd_set rfds;
    struct timeval tv;
    int retval, maxfd = -1;
    char usrname[MAXBUF + 1],hostname[MAXBUF + 1];
    int flag = 1;
 
    printf("请输入您的用户名:");
    bzero(hostname, MAXBUF + 1);
    fgets(hostname, MAXBUF, stdin);

    if (argc != 3) {
        printf
            ("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
             argv[0], argv[0]);
        exit(0);
    }
    /* 创建一个 socket 用于 tcp 通信 */
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket");
        exit(errno);
    }

    /* 初始化服务器端(对方)的地址和端口信息 */
    bzero(&dest, sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_port = htons(atoi(argv[2]));
//int inet_aton(const char *cp, struct in_addr *inp);
//inet_aton() 转换网络主机地址cp为二进制数值,并存储在struct in_addr结构中,即第二个参数*inp,函数返回非0表示cp主机有地有效,返回0表示主机地址无效。
    if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
        perror(argv[1]);
        exit(errno);
    }

    /* 连接服务器 */
    if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
        perror("Connect ");
        exit(errno);
    }
    /*发用户名给服务器*/
    send(sockfd, hostname, strlen(hostname) - 1, 0);
   
    printf ("\n准备就绪,可以开始聊天了……直接输入消息回车即可发信息给对方\n");
    while (1) {
        /* 把集合清空 */
        FD_ZERO(&rfds);
        /* 把标准输入句柄0加入到集合中 */
        FD_SET(0, &rfds);
        maxfd = 0;
        /* 把当前连接句柄sockfd加入到集合中 */
        FD_SET(sockfd, &rfds);
        if (sockfd > maxfd)
            maxfd = sockfd;
        /* 设置最大等待时间 */
        tv.tv_sec = 1;
        tv.tv_usec = 0;
        /* 开始等待 */

        retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
        if (retval == -1) {
            printf("将退出,select出错! %s", strerror(errno));
            break;
        } else if (retval == 0) {
            /* printf
               ("没有任何消息到来,用户也没有按键,继续等待……\n"); */
            continue;
        } else {
            if (FD_ISSET(sockfd, &rfds)) {
        if(flag == 1) {
        /*接收服务器端用户名*/
        bzero(usrname, MAXBUF + 1);
        recv(sockfd, usrname, MAXBUF, 0);
        flag = 0;
        }
        else {
                /* 连接的socket上有消息到来则接收对方发过来的消息并显示 */
                bzero(buffer, MAXBUF + 1);
                /* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
                len = recv(sockfd, buffer, MAXBUF, 0);
                if (len > 0)
                    printf("收到来自%s的消息:'%s',共%d个字节的数据\n",
                         usrname, buffer, len);
                else {
                    if (len < 0)
                        printf("消息接收失败!错误代码是%d,错误信息是'%s'\n",
                             errno, strerror(errno));
                    else
                        printf("对方退出了,聊天终止!\n");
                    break;
                     }
                }
            }
            if (FD_ISSET(0, &rfds)) {
                /* 用户按键了,则读取用户输入的内容发送出去 */
                bzero(buffer, MAXBUF + 1);
                fgets(buffer, MAXBUF, stdin);
                if (!strncasecmp(buffer, "quit", 4)) {
                    printf("自己请求终止聊天!\n");
                    break;
                }
                /* 发消息给服务器 */
                len = send(sockfd, buffer, strlen(buffer) - 1, 0);
        buffer[len]='\0';
//        slen = strlen(hostname);
//        hostname[slen-1] = '\0';
                if (len < 0) {
                    printf
                        ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
                         buffer, errno, strerror(errno));
                    break;
                } else
                    printf("%s的消息:%s发送成功,共发送了%d个字节!\n",
                         hostname, buffer, len);
           
            }
        }
    }
    /* 关闭连接 */
    close(sockfd);
    return 0;
}
编译用如下命令:
gcc -Wall async-server.c -o server
gcc -Wall async-client.c -o client
运行用如下命令:
./server 7838 1
./client 127.0.0.1 7838
原文地址:https://www.cnblogs.com/hucn/p/1597001.html