参考
传送门 - 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;
}
//\
第六步,客户端连接到达时,本服务器需接受连接,注意接受链接用的是客户端的变量即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));
}
}