网络Socket--服务器端源代码解析
2018-03-08 18:50
323 查看
附送网络socket模型传送门http://blog.csdn.net/pipiavenger/article/details/79414863
/* 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);}注:红色表示待注释处
服务器端:(使用多线程来完成目标)
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--客户端的源代码解析
- android json解析及简单例子+Android与服务器端数据交互+Android精彩案例【申明:来源于网络】
- java网络socket编程(三)之ServerSocket服务器端
- C++解析JSON进行网络传输--一个通过JSON方式的socket传输
- 网络编程(3)—— 基于Windows系统的socket服务器端的编程步骤
- linux socket网络编程:fcntl select(多个客户端连接服务器端情形)
- linux网络编程socket服务器端实现
- java网络编程之socket网络编程示例(服务器端/客户端)
- Net网络通讯编程[利用Socket实现字串、文件、序列化对象传输]--前面6篇博文全部源代码下载地址
- 海思板子利用unixctl socket网络编程,进行接口请求和cJSON解析之二
- MFC Socket网络编程之TCP服务器端
- linux网络编程之socket(九):使用select函数改进客户端/服务器端程序
- 利用TCP协议通过Socket编写的网络聊天工具3-服务器端设计
- [置顶] Android网络之HttpUrlConnection和Socket关系解析
- Socket网络编程【域名解析】
- 海思板子利用unixctl socket网络编程,进行接口请求和cJSON解析
- socket网络编程之一:客户端和服务器端接口函数
- Flash Socket安全问题的全面解析 服务器端代码
- socket字节流解析(网络抓包解析)
- Net网络通讯编程[利用Socket实现字串、文件、序列化对象传输]--前面6篇博文全部源代码下载地址