套接字编程3 ------ 一个面向连接的SERVER/CLIENT综合实例
2017-12-21 13:56
399 查看
本示例程序是用TCP套接字开发的模拟用户远程登录的程序。
1、服务器端程序的设计
服务器端的并发性
服务器端程序采用多进程的方式实现对多个客户端连接请求的响应。主程序创建套接字后将其绑定到4507端口。然后使套接字处于监听状态,调用accept函数等待来自客户端的连接请求。每接收一个新的客户端连接请求,服务器端进程就创建一个子进程,该子进程负责处理该连接请求,服务器端进程继续等待来自其他客户端的连接请求。
数据格式
由于TCP是一种基于流的数据传输方式,数据没有固定格式。因此需要在应用程序中定义一定的数据格式,本程序以回车符(字符‘’\n)作为一次数据的结束标志。
用户信息
本程序将用户信息保存在一个全局数组中。服务器端接收到客户端的登录信息后,分别在全局数组中查询是否存在该用户名,以及密码是否匹配,若正确则分别回应字符'y'+结束标志,否则回复'n'+结束标志。用户名、密码都匹配则发送一个欢迎登录的字符串给客户端。
2、客户端程序的设计
客户端主程序创建套接字后调用connect()连接到服务器端4507端口,使用从connect()返回的连接套接字与服务器端进行通信,交换数据。
具体代码如下:
//my_recv.h
#ifndef __MY_RECV_H
#define __MY_RECV_H
#define BUFSIZE 1024
void my_err(const char * err_string, int line);
int my_recv(int conn_fd, char *data_buf, int len);
#endif
运行结果如下:
1、服务器端程序的设计
服务器端的并发性
服务器端程序采用多进程的方式实现对多个客户端连接请求的响应。主程序创建套接字后将其绑定到4507端口。然后使套接字处于监听状态,调用accept函数等待来自客户端的连接请求。每接收一个新的客户端连接请求,服务器端进程就创建一个子进程,该子进程负责处理该连接请求,服务器端进程继续等待来自其他客户端的连接请求。
数据格式
由于TCP是一种基于流的数据传输方式,数据没有固定格式。因此需要在应用程序中定义一定的数据格式,本程序以回车符(字符‘’\n)作为一次数据的结束标志。
用户信息
本程序将用户信息保存在一个全局数组中。服务器端接收到客户端的登录信息后,分别在全局数组中查询是否存在该用户名,以及密码是否匹配,若正确则分别回应字符'y'+结束标志,否则回复'n'+结束标志。用户名、密码都匹配则发送一个欢迎登录的字符串给客户端。
2、客户端程序的设计
客户端主程序创建套接字后调用connect()连接到服务器端4507端口,使用从connect()返回的连接套接字与服务器端进行通信,交换数据。
具体代码如下:
//my_recv.h
#ifndef __MY_RECV_H
#define __MY_RECV_H
#define BUFSIZE 1024
void my_err(const char * err_string, int line);
int my_recv(int conn_fd, char *data_buf, int len);
#endif
//my_recv.c #define MY_RECV_C #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <string.h> #include <errno.h> #include "my_recv.h" /*自定义的错误处理函数*/ void my_err(const char * err_string, int line) { fprintf(stderr, "line:%d ", line); perror(err_string); exit(1); } /* * 函数名: my_recv * 描 述 : 从套接字上读取一次数据(以'\n'为结束标志) * 参 数 : conn_fd -- 从该连接套接字上接收数据 * data_buf -- 读取到的数据保存在此缓冲中 * len -- data_buf所指向的空间长度 * 返回值: 出错返回-1, 服务器端已关闭连接则返回0, 成功返回读取的字节数 */ int my_recv(int conn_fd, char *data_buf, int len) { static char recv_buf[BUFSIZE]; // 自定义缓冲区,BUFSIZE定义在my_recv.h中 static char *pread; // 指向下一次读取数据的位置 static int len_remain = 0; // 自定义缓冲区中剩余字节数 int i; // 如果自定义缓冲区中没有数据,则从套接字读取数据 if (len_remain <= 0) { if ((len_remain =recv(conn_fd, recv_buf, sizeof (recv_buf), 0)) < 0) { my_err("recv", __LINE__); } else if (len_remain == 0) { // 目的计算机端的socket连接关闭 return 0; } pread = recv_buf; // 重新初始化pread指针 } // 从自定义缓冲区中读取一次数据 for (i=0; *pread != '\n'; i++) { if (i > len) { // 防止指针越界 return -1; } data_buf[i] = *pread++; len_remain--; } // 去除结束标志 len_remain--; pread++; return i; // 读取成功 }
//my_server.c // Client/Server模型的服务器端 #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include "my_recv.h" // 自定义的头文件 #define SERV_PORT 4507 // 服务器端的端口 #define LISTENQ 12 // 连接请求队列的最大长度 #define INVALID_USERINFO 'n' // 用户信息无效 #define VALID_USERINFO 'y' // 用户信息有效 #define USERNAME 0 // 接收到的是用户名 #define PASSWORD 1 // 接收到的是密码 struct userinfo { // 保存用户名和密码的结构体 char username[32]; char password[32]; }; struct userinfo users[ ] = { {"linux", "unix"}, {"4507", "4508"}, {"clh", "clh"}, {"xl", "xl"}, {" "," "} // 以只含一个空格的字符串作为数组的结束标志 }; // 查找用户名是否存在,存在返回该用户名的下标,不存在则返回-1,出错返回-2 int find_name(const char *name) { int i; if (name == NULL) { printf("in find_name, NULL pointer"); return -2; } for (i=0; users[i].username[0] != ' ';i++) { if (strcmp(users[i].username, name) == 0) { return i; } } return -1; } // 发送数据 void send_data(int conn_fd, const char *string) { if (send(conn_fd, string, strlen(string), 0) < 0) { my_err("send", __LINE__); // my_err函数在my_recv.h中声明 } } int main() { int sock_fd, conn_fd; int optval; int flag_recv = USERNAME; // 标识接收到的是用户还是密码 int ret; int name_num; pid_t pid; socklen_t cli_len; struct sockaddr_in cli_addr, serv_addr; char recv_buf[128]; // 创建一个TCP套接字 sock_fd = socket(AF_INET, SOCK_STREAM,0); if (sock_fd < 0) { my_err("socket", __LINE__); } // 设置该套接字使之可以重新绑定端口 optval = 1; if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(int)) < 0) { my_err("setsockopt", __LINE__); } // 初始化服务器端地址结构 memset(&serv_addr, 0, sizeof (struct sockaddr_in)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(SERV_PORT); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 将套接字绑定到本地端口 if (bind(sock_fd, (struct sockaddr *)&serv_addr, sizeof (struct sockaddr_in)) < 0) { my_err("bind", __LINE__); } // 将套接字转化为监听套接字 if (listen(sock_fd, LISTENQ) < 0) { my_err("listen", __LINE__); } cli_len = sizeof (struct sockaddr_in); while (1) { // 通过accept接受客户端的连接请求,并返回连接套接字用于收发数据 conn_fd = accept(sock_fd, (struct sockaddr *)&cli_addr, &cli_len); if (conn_fd < 0) { my_err("accept", __LINE__); } printf("accept a new client, ip:%s\n", inet_ntoa(cli_addr.sin_addr)); // 创建一个子进程处理刚刚接受的连接请求 if ( (pid = fork()) == 0 ) { // 子进程 while(1) { if ((ret = recv(conn_fd, recv_buf, sizeof (recv_buf), 0)) < 0) { perror("recv"); exit(1); } recv_buf[ret-1] = '\0'; // 将数据结束标志'\n'替换成字符串结束标志 if (flag_recv == USERNAME) { // 接收到的是用户名 name_num = find_name(recv_buf); switch (name_num) { case -1: send_data(conn_fd, "n\n"); break; case -2: exit(1); break; default: send_data(conn_fd, "y\n"); flag_recv = PASSWORD; break; } } else if (flag_recv == PASSWORD) { // 接收到的是密码 if (strcmp(users[name_num].password, recv_buf) == 0) { send_data(conn_fd, "y\n"); send_data(conn_fd, "Welcome login my tcp server\n"); printf("%s login\n", users[name_num].username); break; // 跳出while循环 } else send_data(conn_fd, "n\n"); } } close(sock_fd); close(conn_fd); exit(0); // 结束子进程 } else { // 父进程关闭刚刚接受的连接请求,执行accept等待其他连接请求 close(conn_fd); } } return 0; }
//my_client.c #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include "my_recv.h" #define INVALID_USERINFO 'n' // 用户信息无效 #define VALID_USERINFO 'y' // 用户信息有效 /*获取用户输入存入到buf,buf的长度为len,用户输入数据以'\n'为结束标志*/ int get_userinfo(char *buf, int len) { int i; int c; if (buf == NULL) { return -1; } i = 0; while ( ((c = getchar()) != '\n') && (c != EOF) && (i < len-2) ) { buf[i++] = c; } buf[i++] = '\n'; buf[i++] = '\0'; return 0; } // 输入用户名,然后通过fd发送出去 void input_userinfo(int conn_fd, const char *string) { char input_buf[32]; char recv_buf[BUFSIZE]; int flag_userinfo; // 输入用户信息直到正确为止 do { printf("%s:", string); if (get_userinfo(input_buf, 32) < 0) { printf("error return from get_userinfo\n"); exit(1); } if (send(conn_fd, input_buf, strlen(input_buf), 0) < 0) { my_err("send", __LINE__); } // 从连接套接字上读取一次数据 if (my_recv(conn_fd, recv_buf, sizeof (recv_buf)) < 0) { printf("data is too long\n"); exit(1); } if (recv_buf[0] == VALID_USERINFO) { flag_userinfo = VALID_USERINFO; } else { printf("%s error,input again,", string); flag_userinfo= INVALID_USERINFO; } } while(flag_userinfo == INVALID_USERINFO); } int main(int argc, char **argv) { int i; int ret; int conn_fd; int serv_port; struct sockaddr_in serv_addr; char recv_buf[BUFSIZE]; // 检查参数个数 if (argc != 5) { printf("Usage: [-p] [serv_port] [-a] [serv_address]\n"); exit(1); } // 初始化服务器端地址结构 memset(&serv_addr, 0, sizeof (struct sockaddr_in)); serv_addr.sin_family = AF_INET; // 从命令行获取服务器端的端口与地址 for (i=1; i<argc; i++) { if (strcmp("-p", argv[i]) == 0) { serv_port = atoi(argv[i+1]); if (serv_port < 0 || serv_port > 65535) { printf("invalid serv_addr.sin_port\n"); exit(1); } else { serv_addr.sin_port = htons(serv_port); } continue; } if (strcmp("-a", argv[i]) == 0) { if (inet_aton(argv[i+1], &serv_addr.sin_addr) == 0) { printf("invalid server ip address\n"); exit(1); } continue; } } // 检测是否少输入了某项参数 if (serv_addr.sin_port == 0 || serv_addr.sin_addr.s_addr == 0) { printf("Usage: [-p] [serv_addr.sin_port] [-a][serv_address]\n"); exit(1); } // 创建一个TCP套接字 conn_fd = socket(AF_INET, SOCK_STREAM,0); if (conn_fd < 0) { my_err("socket", __LINE__); } // 向服务器端发送连接请求 if (connect(conn_fd, (struct sockaddr *)&serv_addr, sizeof (struct sockaddr)) < 0) { my_err("connect", __LINE__); } // 输入用户名和密码 input_userinfo(conn_fd, "username"); input_userinfo(conn_fd, "password"); // 读取欢迎信息并打印出来 if ((ret = my_recv(conn_fd, recv_buf, sizeof (recv_buf))) < 0) { printf("data is too long\n"); exit(1); } for (i=0; i<ret; i++) { printf("%c", recv_buf[i]); } printf("\n"); close(conn_fd); return 0; }
运行结果如下:
相关文章推荐
- JScript|Event]面向事件驱动的编程(二)--实例讲解:将span模拟成超连接
- HttpAsyncClient 做并发长连接的一个实例
- 一个linux下socket编程的例子,client连server
- 基于 TCP (面向连接)和无连接UDP协议的 socket 套接字编程
- 【设计分享】一个面向对象思想的perl编程实例
- 详解Linux服务器最大tcp连接数 网络编程 在tcp应用中,server事先在某个固定端口监听,client主动发起连接,经过三路握手后建立tcp连接。那么对单机,其最大并发tcp连接数是多少?
- 一个java网络编程Socket的例子,实现Server与Client聊天
- JScript|Event]面向事件驱动的编程(二)--实例讲解:将span模拟成超连接
- 一个简单的TCP套接字编程实例
- HttpAsyncClient 做并发长连接的一个实例
- 网络编程C#篇(二):Socket面向连接简单实例
- 网络安全编程_面向连接的套接字_C/S模式
- 实现server程序,支持多个client同时连接,为每一个client分配一个进程
- [Socket网络编程]由于套接字没有连接并且(当使用一个 sendto 调用发送数据报套接字时)没有提供地址,发送或接收数据的请求没有被接受。
- 使用vncviewer成功连接red hat linux server 连接失败:套接字操作尝试一个...
- SAP JCo的Server/Client编程实例
- vSphere Web Client无法连接到一个或多个 vCenter Server 系统(5.5)
- SAP JCo的Server/Client编程实例
- RMI网络编程开发之三 重启server端程序,client端无法连接
- 套接字(socket)编程简单实现server-client聊天程序