参考
传送门 - 1 - csdn - 2112222222222
传送门 - 2 - bilibili - 憧憬少
传送门 - 3 -
要求
- 开发一个聊天程序
- 包含客户端和服务器段
- 编程语言不限
- 要能在两台PC机上运行
如何实现
通过 socket 实现 两台pc之间的聊天
什么是socket
- Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
- 在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
socket 是如何连接的
- 服务器端先初始化Socket
- 然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。
- 在这时如果有个客户端初始化一个Socket,然后连接服务器(connect)
- 如果连接成功,这时客户端与服务器端的连接就建立了
- 客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据
- 最后关闭连接,一次交互结束。
参考文档:传送门
实现环境
- Visual Studio 2013
- windows 7 (机房电脑)
相关问题解决
解决 mircsoft visual studio 2013 无法打开 winsock2.h 头文件
- 确保
#pragma comment (lib, "ws2_32.lib") // 链接ws2_32.lib文件
该语句放置在前面,首先链接ws2_32.lib文件
- 打开 项目
选择 项目属性
配置属性调整平台工具集为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;}//\第六步,客户端连接到达时,本服务器需接受连接,注意接受链接用的是客户端的变量即Cliint 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 的第一个参数是当前socketint 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));}
}