UNIX网络编程简单练习

以下是我电某专业课作业,都比较简单,我就简单记录,写的也比较简陋。

一、

客户端:从命令行读入服务器的IP地址;并连接到服务器;

服务器端:接收客户的连接请求,并显示客户的IP地址和端口号;

客户端的代码如下:

// 客户端程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

int sockfd;
const char* ip_string = "127.0.0.1";
const int port = 65531; 
int main(){
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1){
		printf("%s\n", "套接字创建失败");
		exit(-1);
	}

	struct sockaddr_in srv_addr;
	socklen_t addrlen;
	addrlen = sizeof(srv_addr);
	memset(&srv_addr, 0, addrlen);
	srv_addr.sin_family = AF_INET;
	srv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	srv_addr.sin_port = htons(port);

	while (1){
		if (connect(sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr))
			== -1)
		{
			printf("connect error!\n");
			sleep(1);
			continue;
		}
		else
			break;
	}		
}
服务端的代码如下:

// 服务器端程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

#define PORT 65531
#define BACKLOG 1

int main(){
	int listenfd, connectfd;
	struct sockaddr_in server, client;
	socklen_t sin_size;

	if((listenfd=socket(AF_INET, SOCK_STREAM, 0)) == -1){
		fprintf(stderr, "%s\n", "监听套接字创建失败。");
		exit(-1);
	}

	bzero(&server, sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(PORT);
	server.sin_addr.s_addr = inet_addr("127.0.0.1");
	if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1){
		fprintf(stderr, "%s\n", "bind执行失败。");
		exit(-1);
	}

	if (listen(listenfd, BACKLOG) == -1){
		fprintf(stderr, "%s\n", "监听错误");
		exit(-1);
	}

	sin_size = sizeof(client);
	while(1){
		if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &sin_size)) == -1){
			fprintf(stderr, "%s\n", "accept错误");
			exit(-1);
		}
		printf("%s\n", "一个客户端连接:");
		char *ip = inet_ntoa(client.sin_addr);
		int port = ntohs(client.sin_port);
		printf("%s%s\n", "客户的IP是:", ip);
		printf("%s%d;\n", "客户的端口号是:", port);
	}
}
程序执行的效果如下图所示,先执行server,再执行client。

客户端:

从命令行读入服务器的IP地址;并连接到服务器;

循环从命令行读入一行字符串,并传递给服务器,由服务器对字符串反转,并将结果返回客户程序;

客户程序显示反转后的字符串;

服务器端:

接收客户的连接请求,并显示客户的IP地址和端口号;

接收客户传来的字符串,反转后传递给客户;

客户端的代码如下:

// 客户端程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#define STRSIZE 2048
int sockfd;
const char* ip_string = "127.0.0.1";
const int port = 65531; 
int main(){
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1){
		printf("%s\n", "套接字创建失败");
		exit(-1);
	}

	struct sockaddr_in srv_addr;
	socklen_t addrlen;
	addrlen = sizeof(srv_addr);
	memset(&srv_addr, 0, addrlen);
	srv_addr.sin_family = AF_INET;
	srv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	srv_addr.sin_port = htons(port);

	while (1){
		if (connect(sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr))
			== -1)
		{
			printf("connect error!\n");
			sleep(1);
			continue;
		}
		else
			break;
	}

	char in_string[2048];
	int len = 0;
	while(1){
		printf("%s\n", "请输入一个字符串:");
		scanf("%s", in_string);
		if(send(sockfd, in_string, strlen(in_string), 0) == -1){
			printf("%s\n", "发送失败");
			sleep(1);
			continue;
		}
		len = recv(sockfd, (void *)in_string, STRSIZE, 0);
		if (len == -1){
			printf("%s\n", "接收数据失败");
			exit(-1);
		}
		else{
			in_string[len] = '\0';
		}
		printf("%s\n", in_string);
	}		
}

服务端的代码如下:

// 服务器端程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#define PORT 65531
#define BACKLOG 1
#define STRSIZE 2048

void revstring(char * str, int len){
	int start = 0;
	int end = len - 1;
	while(start != end && start <= end){
		char temp = str[start];
		str[start] = str[end];
		str[end] = temp;
		start++;
		end--;
	}

}


int main(){
	int listenfd, connectfd;
	struct sockaddr_in server, client;
	socklen_t sin_size;

	if((listenfd=socket(AF_INET, SOCK_STREAM, 0)) == -1){
		fprintf(stderr, "%s\n", "监听套接字创建失败。");
		exit(-1);
	}

	bzero(&server, sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(PORT);
	server.sin_addr.s_addr = inet_addr("127.0.0.1");
	if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1){
		fprintf(stderr, "%s\n", "bind执行失败。");
		exit(-1);
	}

	if (listen(listenfd, BACKLOG) == -1){
		fprintf(stderr, "%s\n", "监听错误");
		exit(-1);
	}

	sin_size = sizeof(client);
	char rev_str[STRSIZE];
	int len = 0;
	while(1){
		if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &sin_size)) == -1){
			fprintf(stderr, "%s\n", "accept错误");
			exit(-1);
		}
		printf("%s\n", "一个客户端连接:");
		char *ip = inet_ntoa(client.sin_addr);
		int port = ntohs(client.sin_port);
		printf("%s%s\n", "客户的IP是:", ip);
		printf("%s%d;\n", "客户的端口号是:", port);

		while(1){
			len = recv(connectfd, rev_str, STRSIZE, 0);
			if (len == -1 || len == 0){
				printf("%s\n", "接收数据失败");
				exit(-1);
			}
			rev_str[len] = '\0';
			if (strcmp(rev_str, "exit") == 0){
				close(connectfd);
				continue;
			}
			printf("收到的数据是:%s\n", rev_str);
			revstring(rev_str, strlen(rev_str));
			printf("反转后的数据是:%s\n", rev_str);
			send(connectfd, rev_str, len, 0);
		}
	}
}

程序执行的截图如下图所示:


以上两个程序都比较简单,没有涉及服务器的并发,就是简单的练习一下。

三、

利用多线程技术实现如下并发网络程序,要求对上课时的实现进行完善,利用线程专用数据TSD实现。服务器和客户程序分别实现如下功能:


1、服务器等待客户连接,连接成功后显示客户地址,接着接收该客户的名字并显示,然后接收来自客户的信息(字符串),将该字符串反转,并将结果送回客户。要求服务器具有同时处理多个客户的能力。当某个客户断开连接时,打印所有该客户输入的数据。


2、客户首先与服务器连接,接着接收用户输入客户的名字,将该名字发送给服务器,然后接收用户输入的字符串,并发送给服务器,然后接收服务器返回的经处理后的字符串,并显示之。当用户输入Ctrl+D,终止连接并退出。

当用户输入Ctrl+D时,相当于EOF,所以在代码中当scanf等于EOF时,就close连接,并exit退出进程。

客户代码如下所示:

// 客户端程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#define STRSIZE 2048
int sockfd;
const char* ip_string = "127.0.0.1";
const int port = 65531; 
int main(){
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1){
		printf("%s\n", "套接字创建失败");
		exit(-1);
	}

	struct sockaddr_in srv_addr;
	socklen_t addrlen;
	addrlen = sizeof(srv_addr);
	memset(&srv_addr, 0, addrlen);
	srv_addr.sin_family = AF_INET;

	char * server_addr = (char*) malloc(100);
	printf("%s\n", "请输入服务器端的地址:");
	scanf("%s", server_addr);
	srv_addr.sin_addr.s_addr = inet_addr(server_addr);
	srv_addr.sin_port = htons(port);

	while (1){
		if (connect(sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr))
			== -1)
		{
			printf("connect error!\n");
			sleep(1);
			continue;
		}
		else
			break;
	}

	char in_string[2048];
	
	// 输入客户的名字
	printf("%s\n", "请输入客户的名字!");
	scanf("%s", in_string);
	if(send(sockfd, in_string, strlen(in_string), 0) == -1){
		printf("%s\n", "发送客户名字失败!");
		exit(-1);
	}

	// 客户输入字符串
	int len = 0;
	while(1){
		printf("%s\n", "请输入一个字符串:");
		if (scanf("%s", in_string) == EOF){
			close(sockfd);
			exit(0);
		}
		if(send(sockfd, in_string, strlen(in_string), 0) == -1){
			printf("%s\n", "发送失败");
			sleep(1);
			continue;
		}
		len = recv(sockfd, (void *)in_string, STRSIZE, 0);
		if (len == -1){
			printf("%s\n", "接收数据失败");
			exit(-1);
		}
		else{
			in_string[len] = '\0';
		}
		printf("%s\n", in_string);
	}		
}

在服务端中,利用线程技术来实现并发,在接收到客户的连接后,将sockaddr_in结构和连接套接字组成的结构作为参数传递给线程。在线程中接收和打印用户的信息。

revstring用来反转字符串,thr_fun是线程的执行函数。在thr_fun中,首先利用sockaddr_in结构打印客户端的ip和port信息,并将客户输入的信息存放在vector容器中,在客户退出时,即recv调用返回值小于等于0时,遍历vector容器,然后打印出用户输入的所有信息。

服务端的代码如下:

// 服务器端程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <iostream>
#include <pthread.h>
using namespace std;

#define PORT 65531
#define BACKLOG 1
#define STRSIZE 2048

void revstring(char * str, int len){
	int start = 0;
	int end = len - 1;
	char temp;
	while(start != end && start <= end){
		temp = str[start];
		str[start] = str[end];
		str[end] = temp;
		start++;
		end--;
	}
}

// 需要传递给线程的变量
struct info_pthread{
	int connectfd;
	struct sockaddr_in client;
};

void * thr_fun(void *arg){
	struct info_pthread *struct_ptr = (struct info_pthread *)arg;
	vector<char*> fromClient;

	// 服务端打印客户的ip和端口信息
	printf("%s\n", "一个客户端连接:");
	char *ip = inet_ntoa((struct_ptr->client).sin_addr);
	int port = ntohs((struct_ptr->client).sin_port);
	printf("%s%s\n", "客户的IP是:", ip);
	printf("%s%d;\n", "客户的端口号是:", port);
	
	int connectfd = struct_ptr -> connectfd;
	char* str_rev;
	int len = 0;

	// 打印客户的名称
	str_rev = (char*)malloc(STRSIZE * sizeof(char));
	len = recv(connectfd, str_rev, STRSIZE, 0);
	if (len <= 0){
		printf("%s\n", "接收客户名称的数据失败");
		exit(-1);
	}
	*(str_rev + len) = '\0';
	printf("客户的名称是:%s\n", str_rev);
	fromClient.push_back(str_rev);

	// 循环接收客户的信息
	char * str_rev_cp;
	while(1){
		str_rev = (char*)malloc(STRSIZE * sizeof(char));
		len = recv(connectfd, str_rev, STRSIZE, 0);
		if (len <= 0){
			// 循环输出客户输入过的信息
			cout << "客户连接断开,客户输入过的信息如下:" << endl;
			for (vector<char*>::iterator it = fromClient.begin(); it != fromClient.end(); ++it){
				cout << *it << endl;
				free(*it);				
			}
			free(struct_ptr);
			return NULL;
		}

		*(str_rev + len)= '\0';
		printf("收到来自%s的数据是:%s\n",  fromClient[0], str_rev);
		fromClient.push_back(str_rev);
		str_rev_cp = (char*)malloc(sizeof(char) * strlen(str_rev) + 1);
		strcpy(str_rev_cp, str_rev);
		revstring(str_rev_cp, strlen(str_rev));
		printf("反转后发送给%s的数据是:%s\n", fromClient[0], str_rev_cp);
		send(connectfd, str_rev_cp, len, 0);
		free(str_rev_cp);
	}

}


int main(){
	int listenfd, connectfd;
	struct sockaddr_in server, client;
	socklen_t sin_size;

	if((listenfd=socket(AF_INET, SOCK_STREAM, 0)) == -1){
		fprintf(stderr, "%s\n", "监听套接字创建失败。");
		exit(-1);
	}

	bzero(&server, sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(PORT);
	server.sin_addr.s_addr = inet_addr("127.0.0.1");
	if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1){
		fprintf(stderr, "%s\n", "bind执行失败。");
		exit(-1);
	}

	if (listen(listenfd, BACKLOG) == -1){
		fprintf(stderr, "%s\n", "监听错误");
		exit(-1);
	}

	sin_size = sizeof(client);
	char rev_str[STRSIZE];
	int len = 0;
	int err;
	while(1){
		if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &sin_size)) == -1){
			fprintf(stderr, "%s\n", "accept错误");
			exit(-1);
		}
		pthread_t tid;
		struct info_pthread * struct_ptr = (struct info_pthread *)malloc(sizeof(struct info_pthread));
		struct_ptr->client = client;
		struct_ptr->connectfd = connectfd;
		err = pthread_create(&tid, NULL, thr_fun, (void *)struct_ptr);
		if (err != 0){
			cout << "线程创建错误,错误代码:" << err << endl;
			exit(1);
		}
	}
}

程序执行的截图如下图所示,首先开启服务端,再开启客户端。

这里开启两个客户端程序,并如下图所示输入必要的信息和测试数据,服务端会收到来自客户端的信息,并将它反转后发送给客户。在客户端退出时,会将客户输入的数据全部输出。



服务器利用I/O复用技术,实现同时向多个客户提供服务。要求:
服务器:接收客户连接请求,并打印客户IP地址及端口号,然后接收客户发来的字符串,并打印该字符串和其来自与哪个客户。同时向客户返回该字符串。当某一客户断开连接时,要求服务器打印该客户输入的所有字符。
客户:从命令行接收服务器地址,并向服务器发起连接请求,连接成功后,从标准输入接收字符串并发送给服务器,等待服务器响应并打印接收的信息。

客户端的代码比较简单,如下所示:

// 客户端程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#define STRSIZE 2048
int sockfd;
const char* ip_string = "127.0.0.1";
const int port = 65531; 
int main(){
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1){
		printf("%s\n", "套接字创建失败");
		exit(-1);
	}

	struct sockaddr_in srv_addr;
	socklen_t addrlen;
	addrlen = sizeof(srv_addr);
	memset(&srv_addr, 0, addrlen);
	srv_addr.sin_family = AF_INET;

	char * server_addr = (char*) malloc(100);
	printf("%s\n", "请输入服务器端的地址:");
	scanf("%s", server_addr);
	srv_addr.sin_addr.s_addr = inet_addr(server_addr);
	srv_addr.sin_port = htons(port);

	while (1){
		if (connect(sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr))
			== -1)
		{
			printf("connect error!\n");
			sleep(1);
			continue;
		}
		else
			break;
	}

	char in_string[2048];
	// 客户输入字符串
	int len = 0;
	while(1){
		printf("%s\n", "请输入一个字符串:");
		if (scanf("%s", in_string) == EOF){
			close(sockfd);
			exit(0);
		}
		if(send(sockfd, in_string, strlen(in_string), 0) == -1){
			printf("%s\n", "发送失败");
			sleep(1);
			continue;
		}
		len = recv(sockfd, (void *)in_string, STRSIZE, 0);
		if (len == -1){
			printf("%s\n", "接收数据失败");
			exit(-1);
		}
		else{
			in_string[len] = '\0';
		}
		printf("%s\n", in_string);
	}		
}

服务端的代码如下所示:

服务端主要是用到了select系统调用来实现同时向多个用户提供服务的。

程序中使用了CLIENT结构来存储关于连接客户的一些信息,包括ip,端口,连接套接字fd,地址结构,并在CLIENT结构中使用vector容器来存放用户输入过的信息。

print_err函数封装了对出错的处理。

Close函数封装了关闭连接套接字时需要做的处理,包括打印客户连接的信息,释放客户结构CLIENT中包含的指针等。

handle_recv_msg封装了对接收到客户连接请求的处理,它通过push_back向CLIENT结构的vector容器里压入用户输入的数据,并将收到的信息再传回给客户端。

main函数首先定义了变量,然后创建监听套接字,绑定端口,再进入while循环,通过select调用来判断各个文件描述符是否可读。若可读,则select调用返回,然后再判断监听套接字是否在可读的套接字集中,如果在,则调用accept函数,接受客户,并做一定的处理;循环每个client的fd,判断它们是否是可读的连接套接字,如果是则接收它们的信息,然后调用handle_recv_msg函数来处理。

// 服务器端程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <iostream>
#include <sys/select.h>

using namespace std;

#define PORT 65531
#define BACKLOG 1
#define STRSIZE 2048

typedef struct CLIENT{
	int fd;
	char *ip;
	struct sockaddr_in addr;
	int port;
	char *name;
	vector<char*> str_vec;
} CLIENT;

void print_client_addr(CLIENT *client){
	// 服务端打印客户的ip和端口信息
	printf("%s\n", "一个客户端连接:");
	char *ip = inet_ntoa((client->addr).sin_addr);
	int port = ntohs((client->addr).sin_port);
	printf("%s%s\n", "客户的IP是:", ip);
	printf("%s%d;\n", "客户的端口号是:", port);
	client->port = port;
	client->ip = ip;
}

void print_err(string err_info){
	cout << err_info << endl;
	exit(0);
}

void Close(CLIENT *client){
	// 释放结构中的vector
	vector<char*>::iterator it;
	cout << "客户("<< client->ip <<")的连接断开,客户输入过的信息如下:" << endl;
	for (vector<char*>::iterator it = (client->str_vec).begin(); it != (client->str_vec).end(); ++it){
		cout << *it << endl;
		free(*it);				
	}
	free(client->name);
	close(client->fd);
}

void handle_recv_msg(CLIENT *client, char *msg){
	char* m_msg = (char *)malloc(sizeof(char) * STRSIZE);
	strcpy(m_msg, msg);
	cout << "收到来自("<< (*client).ip << ":" << client->port << ")的消息:"<< msg <<endl;
	(client->str_vec).push_back(m_msg);
	send(client->fd, m_msg, strlen(m_msg), 0);
}

int main(){
	int listenfd, connectfd, sockfd, maxfd, i, maxi, nready, len;
	struct sockaddr_in server;
	socklen_t sin_size;
	fd_set readset, allset;
	CLIENT client[FD_SETSIZE];
	char recvbuf[STRSIZE];

	if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
		fprintf(stderr, "%s\n", "监听套接字创建失败。");
		exit(-1);
	}

	bzero(&server, sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(PORT);
	server.sin_addr.s_addr = inet_addr("127.0.0.1");
	if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1){
		fprintf(stderr, "%s\n", "bind执行失败。");
		exit(-1);
	}

	if (listen(listenfd, BACKLOG) == -1){
		print_err("监听错误");
	}

	maxfd = listenfd;
	sin_size = sizeof(server);
	maxi = -1;
	for (int i = 0; i < FD_SETSIZE; ++i){
		client[i].fd = -1;
	}
	FD_ZERO(&allset);
	FD_SET(listenfd, &allset);
	
	while(1){
		struct sockaddr_in addr;
		readset = allset;
		nready = select(maxfd + 1, &readset, NULL, NULL, NULL);
		if (nready == -1){
			if (errno == EINTR) // 被信号中断后恢复
				continue;
			print_err("select 系统调用出错,进程退出");
		}

		if (FD_ISSET(listenfd, &readset)){
			if ((connectfd = accept(listenfd, (struct sockaddr*)&addr, &sin_size)) == -1){
				print_err("accept错误");	
			}
			for(i = 0; i < FD_SETSIZE; ++i){
				if (client[i].fd < 0){
					client[i].fd = connectfd;
					client[i].name = (char *)malloc(sizeof(char) * STRSIZE);
					client[i].addr = addr;							
					client[i].name[0] = '\0';			
					break;
				}
			}
			if (i == FD_SETSIZE){
				printf("%s\n", "已接收较多的客户端连接!");			
			}
			FD_SET(client[i].fd, &allset);
			print_client_addr(&client[i]);
			if (connectfd > maxfd) 
				maxfd = connectfd;
			if (i > maxi)
				maxi = i;
			--nready;
			if (nready <= 0)
				continue;
		}
		for (i = 0; i <= maxi; ++i){
			if ((sockfd = client[i].fd) < 0)
				continue;
			if (FD_ISSET(sockfd, &readset)){
				len = recv(sockfd, recvbuf, STRSIZE, 0);
				if (len <= 0)
				{
					Close(&client[i]);
					FD_CLR(sockfd, &allset);
					client[i].fd = -1;
				}
				else{
					handle_recv_msg(&client[i], recvbuf);
				}
				if (--nready <= 0)
					break;
			}
		}
	}		
}
程序的执行截图如下图所示,可完成题中给出的要求。






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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值