【计算机网络】大作业-实现两台pc间交流的简单的聊天软件

参考

传送门 - 1 - csdn - 2112222222222
传送门 - 2 - bilibili - 憧憬少
传送门 - 3 -

要求

  1. 开发一个聊天程序
  2. 包含客户端和服务器段
  3. 编程语言不限
  4. 要能在两台PC机上运行

如何实现

通过 socket 实现 两台pc之间的聊天

什么是socket

  • Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
  • 在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

socket 是如何连接的

  1. 服务器端先初始化Socket
  2. 然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。
  3. 在这时如果有个客户端初始化一个Socket,然后连接服务器(connect)
  4. 如果连接成功,这时客户端与服务器端的连接就建立了
  5. 客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据
  6. 最后关闭连接,一次交互结束。

参考文档:传送门

实现环境

  1. Visual Studio 2013
  2. windows 7 (机房电脑)

相关问题解决

解决 mircsoft visual studio 2013 无法打开 winsock2.h 头文件

  1. 确保
#pragma comment (lib, "ws2_32.lib") // 链接ws2_32.lib文件

该语句放置在前面,首先链接ws2_32.lib文件

  1. 打开 项目
    在这里插入图片描述
    选择 项目属性
    在这里插入图片描述配置属性调整平台工具集为XP那一项
    在这里插入图片描述
    记得点击应用,然后确定

解决无法打开 “stdafx.h” 文件的问题

microsoft visual studio 2013版本已经提前帮助项目预编译该文件了,所以不需要include
在这里插入图片描述

编译运行产生方法不安全提示时解决办法

在这里插入图片描述
server于client都将SDL检查设置为否

结果代码

服务器端代码

//server
#pragma comment(linker, "/STACK:36777216")
//#pragma GCC optimize ("O2") 
/**
*    This code has been written by YueGuang, feel free to ask me question. Blog: http://www.moonl1ght.xyz
*    created:
*/
#define LOCAL
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

//#include <tr1/unordered_set>
//#include <tr1/unordered_map>
//#include <array>

using namespace std;


// un template header


#pragma comment (lib, "ws2_32.lib") // 链接ws2_32.lib文件
#include <Winsock2.h> //windows socket编程头文件

//自定义头文件

//}/* .................................................................................................................................. */

/*
bug 说明区域
1.颜色设置setcolor还不能使用
*/

/*
变量解释说明区域 QAQ
*/

// 全局常量
const int BUF_SIZE = 2048;
const int SEND_SIZE = 1000;
const int MAX_BUF_SIZE = 500;
const int NICKNAME_LEN = 20;
const int MAX_CLIENT_COUNT = 20;

// 全局变量
SOCKET sockSer, sockCli;
SOCKADDR_IN addrSer, addrCli;// address 地址  客户端地址和服务器地址
SOCKET NewConnection; //用于接受来自客户端的链接

int clientCount = 0;
int naddr = sizeof(SOCKADDR_IN);
int Ret;
char sendbuf[BUF_SIZE];
char inputbuf[BUF_SIZE];
char recvbuf[BUF_SIZE];



//该结构体的目的是允许多台PC机对服务器进行访问

//全局函数


using namespace std;

int main(){
	//\
									第一步,加载socket函数,载入socket库

	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){
		//setColor(COLOR_ERROR);
		cout << "呐呐呐,载入socket库失败!" << '\n';
		system("pause");
		return 0;
	}


	//\
								第二步,创建一个监听套接字sockser, 创建socket(地址描述(AF_INET格式 - ipv4),指定socket类型(使用的是流式套接字即TCP协议), 指定协议

	if ((sockSer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET){
		printf("当前为无效套接,程序结束");
		system("pause");
		return 0;
	}

	//\
								第三步,初始化服务器的地址包,填写相关信息
	/*
	*	1.AF_INET优先赋值,这是由于该值是告诉winsock我们使用的是ip地址簇
	*	2.填写用来通讯的ip地址
	*	3.填写端口号
	*
	*/
	char ip[] = "192.168.81.90";
	cout << "呐呐呐,本地IP是" << ip << "该电脑已经开启!\n";
	addrSer.sin_family = AF_INET;
	addrSer.sin_port = htons(5000);
	addrSer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	



	//\
							第四步,将创建的sockser套接字和上面填写的相关地址信息绑定在一起,bind函数有很多,我们要选择的是sock中的所以加头文件的时候要注意不要多加

	if (bind((SOCKET)sockSer, (SOCKADDR *)&addrSer, sizeof(addrSer)) == SOCKET_ERROR)
	{
		printf("BIND_ERROR: %d\n", SOCKET_ERROR);
		return 0;
	}

	
	cout << "二次元世界连接成功!" << endl;
	

	//\
						  第五步,让服务器Socket开启监听,并且设置最大的等待连接数,等待连接数(半连接)过大会给服务器造成负载
	
	if (listen(sockSer, 5) == SOCKET_ERROR){
		printf("LISTEN_ERROR: %d\n", SOCKET_ERROR);
		system("pause");
		return 0;
	}


	//\
							第六步,客户端连接到达时,本服务器需接受连接,注意接受链接用的是客户端的变量即Cli
	
	int ClientAddrLen = sizeof(addrCli);
	printf("正在接受连接...");
	if ((NewConnection = accept(sockSer, (SOCKADDR *)&addrCli, &ClientAddrLen)) == INVALID_SOCKET)
	{
		printf("ACCPET_ERROR: %d\n", INVALID_SOCKET);
		closesocket(sockSer);
		return 0;
	}
	printf("检测到一个来自三次元的连接: %s 端口:%d\n", inet_ntoa(addrCli.sin_addr), ntohs(addrCli.sin_port));


	//\
							第七步,开始接听,true情况下进程不关闭就不会结束,但需要考虑电脑
	while (true)
	{
		//接收数据
		Ret = recv(NewConnection, recvbuf, BUF_SIZE, 0);
		if (Ret > 0)
			printf("JOJO对你说: %s\n", recvbuf);
		else if (Ret < 0)
			printf("RECV_ERROR: %d\n", SOCKET_ERROR);
		else
		{
			printf("对方觉得二次元浓度过高,退出了聊天!");
			break;
		}

		//发送数据
		printf("\n说:");
		scanf("%s", sendbuf);
		if (strcmp(sendbuf, "quit") == 0)   //退出
			break;
		if (send(NewConnection, sendbuf, BUF_SIZE, 0) == SOCKET_ERROR)
		{
			printf("消息发送失败!\n");
			break;
		}
	}

	//关闭连接
	shutdown(NewConnection, SD_BOTH);
	closesocket(NewConnection);

	//关闭socket库
	closesocket(sockSer);

	//清空加载项
	if (WSACleanup() == SOCKET_ERROR)
	{
		printf("WSACLEANUP_ERROR: %d\n", WSAGetLastError());
		return 0;
	}

	system("pause");
	return 0;
}

客户端代码

#pragma comment(linker, "/STACK:36777216")
//client
//client
#pragma comment(linker, "/STACK:36777216")
//#pragma GCC optimize ("O2") 
/**
*    This code has been written by YueGuang, feel free to ask me question. Blog: http://www.moonl1ght.xyz
*    created:
*/

#include <algorithm>
#include <iostream>
#include <stdlib.h>
#include <stdio.h>


//#include <tr1/unordered_set>
//#include <tr1/unordered_map>
//#include <array>

using namespace std;

// un template header


#pragma comment (lib, "ws2_32.lib") // 链接ws2_32.lib文件
#include <Winsock2.h> //windows socket编程头文件


/*---------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/





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

//全局变量
SOCKET sockSer, sockCli;
SOCKADDR_IN addrSer, addrCli;// address 地址  客户端地址和服务器地址
int naddr = sizeof(SOCKADDR_IN);
char sendbuf[BUF_SIZE];
char inputbuf[BUF_SIZE];
char recvbuf[BUF_SIZE];
int Ret;

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

		}
	}
}


int main(){
	//\
							加载socket函数,载入socket库

	WSADATA WSAData;
	if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0){
		//setColor(COLOR_ERROR);
		cout << "呐呐呐,载入socket库失败!" << '\n';
		system("pause");
		return 0;
	}


	char ip[] = "192.168.81.90";
	cout << "呐呐呐,本地IP是" << ip << "该电脑已经开启!\n";

	//setColor(COLOR_NORMAL);


	//初始化服务器地址
	addrSer.sin_family = AF_INET;
	addrSer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addrSer.sin_port = htons(5000);
	//创建socket(地址描述(AF_INET格式 - ipv4),指定socket类型(使用的是流式套接字即TCP协议), 指定协议
	sockSer = socket(AF_INET, SOCK_STREAM, 0);

	//建立连接
	if (connect(sockSer, (SOCKADDR *)&addrSer, sizeof(SOCKADDR)) == SOCKET_ERROR)
	{
		cout << "CONNECT_ERROR : " << SOCKET_ERROR << endl;
		return 0;
	}
	else
	{
		cout << "二次元世界,连接成功!" << endl;
	}


	//读取用户名
	char username[50];
	printf("请输入您的用户名: ");
	scanf("%s", username);


	const int max_connet_cnt = 20; //最大尝试连接次数
	int cnt = 0;

	while (true){

		//发送数据
		cout << '\n' << username << "说:";
		cin >> sendbuf;
		if (strcmp(sendbuf, "quit") == 0)
		{
			break;
		}
		if (send(sockSer, sendbuf, BUF_SIZE, 0) == SOCKET_ERROR)
		{
			cout << "消息发送失败" << endl;
			break;
		}

		//接收数据
		Ret = recv(sockSer, recvbuf, BUF_SIZE, 0);
		if (Ret < 0)
		{
			cout << "RECV_ERROR" << SOCKET_ERROR << endl;
			break;
		}
		else if (Ret == 0)
		{
			cout << "对方退出聊天程序,聊天结束" << endl;
			break;
		}
		else
		{
			cout << "Server对你说:" << recvbuf << endl;
		}

	}
	//关闭socket库
	closesocket(sockSer);
	closesocket(sockCli);
	WSACleanup(); //清空加载项

	return 0;
}

运行截图1

在这里插入图片描述

获取本地IP的函数

void getLocalIP(char localIp[], int n){
	gethostname(localIp, n);
	HOSTENT *host = gethostbyname(localIp);
	in_addr PcAddr;
	int i = 0;

	while (true){
		char * p = host->h_addr_list[i];
		if (p == NULL){
			break;
		}
		memcpy(&(PcAddr, S_un.S_addr), p, host->h_length);
		strcpy(localIp, inet_ntoa(PcAddr));

	}
}
  • 8
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值