您的位置:首页 > 理论基础 > 计算机网络

网络Socket--服务器端源代码解析

2018-03-08 18:50 323 查看
附送网络socket模型传送门http://blog.csdn.net/pipiavenger/article/details/79414863

服务器端:(使用多线程来完成目标)

    1.服务器端程序通过命令行参数指定服务器端的参数;    2.服务器端验证客户端传过来的用户名和密码进行验证,如果出错将错误原因返回给客户端;    3.如果认证通过,则客户端发过来将数据回射回去;

代码如下:

socket_server_thread.c
/* Some Unix Program Standard head file */#include<stdio.h>    /* 基础头文件printf()等 */#include<stdlib.h>    #include<unistd.h>    #include<string.h>    /* 字符串匹配等调用头文件 */#include<pthread.h>    /* 多线程调用所需头文件 */#include<getopt.h>    /* getopt_long */#include<libgen.h>    /* basename() */
/* Socket Program head file */#include<sys/types.h>    /* 网络通信必包含文件 */#include<sys/socket.h>    /* setsocketopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); */#include<netinet/in.h>    /* socketaddr_in{} and other Internet define */
/* strerror(), perror(), errno head file */#include<errno.h>#include<time.h>    /* 内联函数中调用struct timeval */
#define DAYTIME_SERV_PORT                 6000#define MAX_BUF_SIZE                           1024#define LISTENQ                                     1024    /*2nd argument to listen() */
#define DEF_USER                                    "guozhihao"#define DEF_PASSWD                               "qw136025"

void *socket_client_worker(void *arg);    /* 声明socket_client_worker指针函数供pthread_create()里参数调用 */

void print_usage(char * progname)    /*打印使用该文件的提示信息*/{    printf("Usage: %s [OPTION]...\n",progname);
    printf(" %s is a socket server program, which used used to verify client and echo back string from it\n", progname);    printf("\nMandatory arguments to long options are mandatory for short options too:\n");
    printf(" -b[daemon    ]    set program running on background\n");    printf(" -p[port    ]    Socket server port address\n");    printf(" -h[help    ]    Display this help information\n");    
    printf("\nExample: %s -b -p 8888,progname");    return ;}

int main(int argc, char **argv){    int                                 listenfd,connfd;    /* 定义监听文件描述符和连接文件描述符 */    struct sockaddr_in         servaddr;        /* 定义网络通信通用兼容IPV4/IPV6 */    int                                 serv_port = 0;    /* 用来存放服务器端口初始化 */    int                                 daemon_run = 0;    /* 将程序放在后台执行开关 */    char                              *progname = NULL;    /* 文件名指针初始化 */    pthread_t                      tid;    /* 声明tid为pthread_t类型pthread_create()作为参数调用 */    int                                 opt;    /* 供给函数switch()使用参数 */        int                                 on = 1;    /* 供给setsockopt使用 */

struct option             long_options[] =    /* 供给getopt_long()作为结构体参数使用具体可参考man getopt_long() */    {        {"daemon",required_argument,NULL, 'b'},        {"port",required_argument,NULL, 'p'},        {"help",no_argument,NULL, 'h'},        {NULL,0,NULL,0}    };
    progname = basename(argv[0]);    /* 获取文件名 */
    /* Parser the command line parameters 解析命令行参数,如参数b、p、h */    while((opt = getopt_long(argc,argv,"bp:h",long_options,NULL))!= -1)        {            switch(opt)        {            case 'b':     /* -b将程序放到后台运行 */                    daemon_run = 1;    /* 打开后台运行值 */                    break;
            case 'p':    /* -p利用函数atoi()转换获取端口 */                    server_port = atoi(optarg);                        break;
            case 'h':    /* -h打印使用说明Get help information */                    print_suage(progname);                    return EXIT_SUCCESS;                           default:                    break;        }    }
    if(0 == serv_port)    /* 判断使用者是否初始化了服务器端口 */    {        printf_usage(progname);        return -1;    }        /* set progname running on background将程序放在后台运行 */    if( daemon_run )    {        daemon(0,0);    }        /* Open an IPV4(AF_INET)TCP(SOCK_STREAM) Socket File Description(listenfd), UDP socket should use SOCK_DGRAM, We can use linux command "man socket" to see this functionmanual创建一个socket使用IPV4,遵守TCP原则,类型:TCP报文 */    if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)    {        /* strerror() is the 1st way to display the failure reason, argument errno is a globle variable defined in <errno.h>, we can use linux command "man strerror" to see this function manual 调用函数strerrno(errno)指出出错原因 */        printf(" Use socket() to create a TCP socket failure: %s\n,"strerror(errno));        return -1;    }    /* Set socket port reuseable设置套接字端口可重用setsockopt用法传送门: *https://www.cnblogs.com/eeexu123/p/5275783.html */    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    /* Now we set the Server Information, include IPV4 or IPV6, Listen IP address and Port 设置服务器信息*/    memset(&servaddr, 0, sizeof(servaddr));    /* 清空结构体servaddr内容并赋值为0 */    servaddr.sin_family = AF_INET;    /* Set it as IPV4 protocal */    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);    /* Listen all the local IP address */    servaddr.sin_port = htons(serv_port);    /* daytime server port */    /* When a socket is created with socket(2), it exists in a name space(address family) but has no address assigned to it. bind() the address specified to by addr tothe socket referred to by the file descriptor listenfd. We can use Linux command "man 2 bind" to see this function manual.当一个套接字使用套接字(2)创建时,它存在于一个名称空间(地址家庭)中,但没有分配给它的地址。bind()将addr指定到由文件描述符所引用的套接字所指定的地址。我们可以使用Linux命令“man 2 bind”来查看此功能手册。 */    if(bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)    /* 判断监听端口是否分配了地址 */    {        printf("Use bind() to bind the TCP socket failure: %s\n,"strerror(errno));        goto CleanUp;    }
    /* listen() marks the socket referred to by listenfd as a passive socket, that is, as a socket that will be used to accept incoming connection requests using accept(2). We can use Linux command "man listen" to see this function manual.listen()是将listenfd所引用的套接字作为一个被动的套接字,也就是说,作为一个套接字,用于接受使用accept(2)的传入连接请求。我们可以使用Linux命令“man listen”来查看此功能手册。 */    if(listen(listenfd, LISTENQ) < 0)    /* 判断监听端口是否接收到请求 */    {        printf("Use bind() to bind the TCP socket failure: %s\n", strerror(errno));        goto CleanUp;    }    printf("%s server start to listen port %d\n", argv[0], serv_port);

    for( ; ; )    /* 此循环表示创建线程,每接收到一个accept就创建一个线程 */    {        /* The accept() system call is used with connection-based socket types (SOCK_STREAM, SOCK_SEQPACKET).It extracts the first connection request on the queue of pending connections for the listening socket listenfd, creates a new connected socket(connfd), and to that socket. The newly created socket is not in the listening state. The original socket listenfd is unaffected by this call.accept()系统调用使用基于连接的套接字类型(SOCK_STREAM, SOCK_SEQPACKET)。它在监听套接字监听器的挂接连接的队列中提取第一个连接请求,创建一个新的连接套接字(connfd)和该套接字。新创建的套接字不在监听状态。原来的套接字监听器不受这个调用的影响。 */        if((connfd=accept(listenfd, (struct sockaddr *)NULL, NULL)) > 0)        {            pthread_create(&tid, NULL, socket_client_worker,(void *)connfd);        }    }
CleanUp:    /* 关闭监听接口 */    close(listenfd);    /* We must close socket File Description when program exit */    return 0;}
static inline msleep(unsigned long ms)    /* 内联函数ms延时 */{    struct timeval         tv;    /* 时间结构体 */    tv.tv_sec = ms/1000;    /* 0.5s */    tv.tv_usec = (ms%1000)*1000;    /*  */
    select(0, NULL, NULL, NULL, &tv);    /* 延时tv地址上的值毫秒 */}

char             *fail_str[3]={"Verify user account get wrong format","Verify user account get wrong username","Verify user account get wrong password"};    /* 出错字符串集第一个为格式出错 */int verify_user_logon(char *buf)    /* 处理登录信息 */{    char                 *ptr = NULL;
    if(!(ptr = strchr(buf,':')))    /* 格式 */    {        return 1;    }
    if(strncmp(buf, DEF_USER, strlen(DEF_USER)))    /* 登录用户 */    {        return 2;    }
    if(strncmp(ptr+1, DEF_PASSWD, strlen(DEF_PASSWD)))    /* 登陆密码 */    {        return 3;    }        return 0;}
    void *socket_worker(void *arg)    /* 创建出来的子线程所执行的权力函数 */{    int                     client_fd;    /* 客户端文件描述符 */    char                  buf[512];    /* 缓冲区 */    int                     rv;
    if(!arg)    /* 判断参数是否被赋值 */    {        printf("Invalid arg\n");        return NULL;    }
    client_fd = (int) arg;    /* arg 为(void *)需要强制类型转换为int */    memset(buf,0,sizeof(buf));    /* 清空缓冲区 */    if((rv = read(client_fd,buf,sizeof(buf)))< = 0)    /* 判断客户端是否连接或者读数据是否出错 */    {        printf("socket disconnect or read get error\n");        goto CleanUp;    }
    if(0 != (rv=verify_user_logon(buf)))    /* 判断客户端登陆用户信息(用户名、密码)是否出错 */    {        printf("client logon account [%s] invalid, disconnect it now\n",buf);        write(client_fd,fail_str[rv-1],strlen(fail_str[rv-1]));    /* 向客户端发送错误信息字符串 */        goto CleanUp;    }
     write(client_fd,"passed",6);    /* 向客户端发送passed */
    while(1)    /* 接收客户端信息 */    {        memset(buf, 0, sizeof(buf));    /* 对缓冲区清零 */          if((rv = read(client_fd, buf, sizeof(buf))) <= 0)    /* 在接受客户端信息时,客户端突然断开,则返回值为负值,如果是读取不到信息为空,则说明没有读取到信息返回值为0 */        {            printf("socket disconnect or read get error\n");            goto CleanUp;        }                /* echo the data from client back将读取到的字节数返回给客户端 */        write(client_fd,buf,rv);        } 
CleanUp:    /* 最终清理关闭服务器 */    printf("Thread worker for socket[%d] exit.\n",client_fd);       msleep(500);        close(client_fd);}注:红色表示待注释处
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Socket Linux