套接字编程3 ------ 一个面向连接的SERVER/CLIENT综合实例

      本示例程序是用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

//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;
}

运行结果如下:






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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值