计算机网络课程设计之网络聊天程序的设计与实现

TCP和UDP在接收方的区别(实际上本实验中用不着)

UDP 协议(User Datagram Protocol 用户数据报协议),是一 种保护消息边界的,不保障可靠数据的传输。就是指传输协议把数据当作一条独立的消息在网上传输,接收端只能接收独立的 消息。也就是说存在保护消息边界,接收端一次只能接收发送端发出的一个数据包

TCP 协议(Transmission Control Protocol 传输控制协议), 是一种流传输的协议。他提供可靠的、有序的、双向的、面向连接的传输。 如果发送端连续发送数据,接收端有可能在一次接收动作中, 会接收两个或者更多的数据包

举例来说,假如,我们连续发送三个数据包,大小分别是 2k、4k、8k,这三个数据包都已经到达 了接收端的网络堆栈中,如果使用 UDP 协议,不管我们使用多大的接收缓冲区去接收数据,我们必须 有三次接收动作,才能够把所有的数据包接收完。而使用 TCP 协议,我们只要把接收的缓冲区大小设 置在 14k 以上,我们就能够一次把所有的数据包接收下来,只需要有一次接收动作。

正文:实验部分

思路:具体学习参考B站大佬,他的代码无法实现全双工,原因是Socket开启了阻塞,我改成非阻塞之后就出现了意料之外的错误,由于后面还有别的实验要做,这里就没修改了。
先贴一份实现简易聊天功能的代码,主要是注解。这是实现聊天室的基础
#include "pch.h"
#include <stdio.h> 
#include <Winsock2.h> 

#pragma comment( lib, "WSOCK32.LIB")

void main() {


	printf("服务器 SocketDLL初始化");
	/****** SocketDLL的初始化 ******/
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(1, 1);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0) { return; }
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
	{
		WSACleanup();   return;
	}

	/**创建服务器Socket**/

	// AF_INET : 使用IP地址族
	// 套接字类型为SOCK_STREAM流类型, 也就是适用于TCP
	// UDP的话是SOCK_DGRAM 数据报套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);



	/*****初始化服务器的包结构********/

	// 封装成sockaddr_in 类,俗称地址包,保存端口号和IP地址
	SOCKADDR_IN addrSrv;
	// 将IP地址 INADDR_ANY (0x00000)主机字节转换成网络字节:高字节在前面
	//addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	// 我们弃用这种默认值,使用局域网中的IP
	char ip[] = "127.0.0.1";
	// inet_addr:将点分十进制的IP 转换成二进制,然后再转换成网络字节
	addrSrv.sin_addr.S_un.S_addr = inet_addr(ip);
	// 设置为IP协议族
	addrSrv.sin_family = AF_INET;
	// 设置端口号,把主机字节转换成网络字节
	addrSrv.sin_port = htons(6000);


	/***将服务器Socket和服务器包进行绑定*****/

	// 强制转换的作用在于:SOCKADDR_IN 地址包的形式,只是方便了我们设置参数
	// 而使用的时候,bind还是需要把这些参数揉和到一起。
	bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));


	// 让服务器Socket开启监听,并且设置最大的等待连接数
	// 等待连接数(半连接)过大会给服务器造成负载
	listen(sockSrv, 5);

	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);
	while (1) {
		SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);
		char sendBuf[50];
		// inet_ntoa 是把二进制IP转换成点分十进制,是上面那个的逆
		// 调用springf将字符串转换成 适合传输的类型
		sprintf(sendBuf, "Welcome %s to here!", inet_ntoa(addrClient.sin_addr));
		// 进行发操作
		send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
		char recvBuf[50];
		// 接收操作
		recv(sockConn, recvBuf, 50, 0);
		printf("%s\n", recvBuf);
		closesocket(sockConn);
	}
}

简易客户端

#include <stdio.h>
#include <Winsock2.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
send(sockClient,"hello",strlen("hello")+1,0);
char recvBuf[50];
recv(sockClient,recvBuf,50,0);
printf("%s\n",recvBuf);
closesocket(sockClient);
WSACleanup();
}

下面的代码是我写的实现全双工和多线程处理,不过存在BUG

客户端


#include "pch.h"
#include <Winsock2.h> 
#include <cstdlib>
#include <stdio.h> 
#include <iostream>

#pragma comment(lib, "ws2_32.lib")

using namespace std;

// 全局常量
const int SEND_SIZE = 1000;
const int MAX_BUF_SIZE = 200;


// 全局变量
SOCKET sockClient;
SOCKADDR_IN addrSrv;

// 接收线程的设置是死循环不断得提交recv申请,如果有反馈,就输出。
DWORD WINAPI Client_Receive_Thread(LPVOID lp) {
	SOCKET *s = (SOCKET*)lp;
	int nrecv;
	while (true)
	{
		// 监听服务器端消息
		char recvBuf[SEND_SIZE];
		// recv 的第一个参数是当前socket
		int res = recv(sockClient, recvBuf, SEND_SIZE, 0); // 最后参数设置成0,表示非阻塞
		if (res > 0) // 由于socket默认的阻塞,因此recv会自动阻塞
		{
			printf("%s\n", recvBuf);

		}
	}
}

void main() {
	printf("客户端 SocketDLL初始化");
	/****** SocketDLL的初始化 ******/
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(1, 1);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0) { return; }
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
		WSACleanup();   return;
	}


	sockClient = socket(AF_INET, SOCK_STREAM, 0);
	unsigned long ul = 1;
	int ret = ioctlsocket(sockClient, FIONBIO, (unsigned long *)&ul);
	if (ret == SOCKET_ERROR) {
		printf("非阻塞化失败!");
		return;
	}
	/****设置地址包*****/
	// 巡回测试IP
	addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.86");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);

	char username[50];
	printf("请输入你的姓名: ");
	scanf("%s", username);


	for(int i=1;i<=5;i++)// 非阻塞下,不知道如何建立连接了。
	{
		cout << "尝试发送连接请求...." << endl;
		// 发起到服务器的连接,第二个参数设置了服务器端的参数
		int ret = connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
		if (ret == 0)
		{
			cout << "连接成功" << endl;
			// 建立连接,且第一次发送username
			send(sockClient, username, SEND_SIZE, 0);
			break;
		}
		cout << "连接失败" << endl;
	}
	

	// 客户端开启一个线程,负责监听服务器端
	LPVOID *lp = (LPVOID*)&sockClient;
	HANDLE hThread = CreateThread(NULL, 0, Client_Receive_Thread, lp, 0, NULL);

	// 主线程用于输入
	char input[50];
	char msg[MAX_BUF_SIZE];
	while (true)
	{

		scanf("%s", input);

		if (strcmp(input, "exit") == 0)
		{
			printf("bye\n");
			break;
		}
		else if (strcmp(input, "send") == 0) {
			scanf("%s", msg);
			printf("success send msg: %s\n", msg);
			send(sockClient, msg, SEND_SIZE, 0);
		}
	}
	// 关闭连接
	closesocket(sockClient);
	WSACleanup();
}

服务器端

#include "pch.h"
#include <stdlib.h>
#include <stdio.h>
#include <WinSock2.h>
#include <iostream>
#include <WS2tcpip.h>
#include <cstring>

#pragma comment(lib,"ws2_32.lib")
using namespace std;



// 常量

const int SEND_SIZE = 1000;
const int MAX_BUF_SIZE = 500;
const int NICKNAME_LEN = 20;
const int MAX_CLIENT_COUNT = 20;

// 变量

int clientCount = 0;



// 封装成Client结构
struct Client
{
	SOCKET s;
	SOCKADDR_IN sin;
	char name[NICKNAME_LEN];
}Cli[MAX_CLIENT_COUNT];



DWORD WINAPI ServerListeningConnect(LPVOID lp) {
	SOCKET *s = (SOCKET*)lp;
	int nrecv;
	int len = sizeof(SOCKADDR);
	while (true)
	{
		Cli[clientCount + 1].s = accept(*s, (SOCKADDR*)&Cli[clientCount + 1].sin, &len);

		// 这里不清楚是否是阻塞,因此加上判断
		if (Cli[clientCount + 1].s != INVALID_SOCKET)
		{
			clientCount++;

			unsigned long ul = 1;
			int ret = ioctlsocket(Cli[clientCount].s, FIONBIO, (unsigned long *)&ul);
			if (ret == SOCKET_ERROR) {
				printf("非阻塞化失败!");
				return 0;
			}

			// 获取客户端的姓名
			recv(Cli[clientCount].s, Cli[clientCount].name, SEND_SIZE  , 0);
			 
			// 反馈。不晓得能反馈到哪
			
			 
			for (int i = 1; i <= clientCount; i++)
			{
				char sendBuf[MAX_BUF_SIZE] = "Welcome [";
				strcat(sendBuf, Cli[i].name);
				strcat(sendBuf, "] 加入直播间,当前直播间有");
				char number[50];
				itoa(clientCount, number, 50);
				strcat(sendBuf, number);
				strcat(sendBuf, "人");
				printf("%s\n", sendBuf);
				send(Cli[i].s, sendBuf, SEND_SIZE, 0);
				
			}
			

			
		}

	}
}

int main()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(1, 1);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0) {
		return 0 ;
	}
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
		WSACleanup();
		return 0;
	}

	printf("初始化成功。。。。\n");
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
	unsigned long ul = 1;
	int ret = ioctlsocket(sockSrv, FIONBIO, (unsigned long *)&ul);
	if (ret == SOCKET_ERROR) {
		printf("非阻塞化失败!");
		return 0;
	}

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.86");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);
	bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
	listen(sockSrv, 5);


	// 开启线程监听连接

	LPVOID *lp = (LPVOID*)&sockSrv;
	HANDLE hThread = CreateThread(NULL, 0, ServerListeningConnect, lp, 0, NULL);


	// 开启线程监听是否客户端发送了消息
	//就写在主线程吧
	// 
	
	while (true)
	{

		char recvbuf[MAX_BUF_SIZE];
		for (int i = 1; i <= clientCount; i++)
		{
			// 当收到消息时。这里有点担心recv是阻塞的,这样的话收到第一个后,就会卡住
			if (recv(Cli[i].s, recvbuf, sizeof(recvbuf), 0) != INVALID_SOCKET)
			{
				// 向另外的客户端进行发送
				char sendbuf[SEND_SIZE];
				strcpy(sendbuf, "[");
				strcat(sendbuf, Cli[i].name);
				strcat(sendbuf, "]:>");
				strcat(sendbuf, recvbuf);
				for (int j = 1; j <= clientCount; j++)
				{
					if (j == i)
						continue;
					
					printf("服务器接收到了来自%d 发给 %d  的 %s\n", i,j,recvbuf);
					printf("服务器即将发送 %s\n", sendbuf);
					send(Cli[j].s, sendbuf, SEND_SIZE, 0);
				}
			}
		}
	}
}

 

猜你喜欢

转载自blog.csdn.net/qq_37591656/article/details/85326486