參考
傳送門 - 1 - csdn - 2112222222222
傳送門 - 2 - bilibili - 憧憬少
要求
- 開發一個聊天程式
- 包含客戶端和服務器段
- 編程語言不限
- 要能在兩臺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檢查設定為否
結果代碼
服務器端代碼
#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 <functional>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <numeric>
#include <cstring>
#include <climits>
#include <cassert>
#include <complex>
#include <cstdio>
#include <string>
#include <vector>
#include <bitset>
#include <queue>
#include <stack>
#include <cmath>
#include <ctime>
#include <list>
#include <set>
#include <map>
#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 地址 客戶端地址和服務器地址
int clientCount = 0;
int naddr = sizeof(SOCKADDR_IN);
char sendbuf[BUF_SIZE];
char inputbuf[BUF_SIZE];
char recvbuf[BUF_SIZE];
//特殊變數
struct Client{
SOCKET s;
SOCKADDR_IN sin;
char name[NICKNAME_LEN];
}Cli[MAX_CLIENT_COUNT];
//該結構體的目的是允許多臺PC機對服務器進行訪問
//全域函式
//服務器接聽連接的實作
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);
}
}
}
}
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;
}
cout << "吶吶吶,初始化成功!" << '\n';
//創建socket(地址描述(AF_INET格式 - ipv4),指定socket型別(使用的是流式套接字即TCP協議), 指定協議
sockSer = 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 可以打開cmd,輸入ipconfig進行查詢
*/
char ip[] = "192.168.81.90";
/* 函式獲取本地IP的方法如下,但是microsoft visual studio 2013 不具備localipget的功能所以不使用該方法
* getLocalIP(localIP, sizeof(localIP) / sizeof(char));
* setColor(COLOR_INFO);
*/
cout << "吶吶吶,本地IP是" << ip << "該電腦已經開啟!\n";
// inet_addr:將點分十進制的IP 轉換成二進制,然后再轉換成網路位元組
addrSrv.sin_addr.S_un.S_addr = inet_addr(ip);
// 設定為IP協議族
addrSrv.sin_family = AF_INET;
// 設定埠號,把主機位元組轉換成網路位元組,根據查詢1024以上隨便寫,最大是 65535
addrSrv.sin_port = htons(6000);
/*
* 需要使用bind函式,將服務器Socket和服務器包進行系結
*/
// 強制轉換的作用在于:SOCKADDR_IN 地址包的形式,只是方便了我們設定引數
// 而使用的時候,bind還是需要把這些引數揉和到一起,
//在上面我們創建的socket名字是sockSer所以這里填socket應該是sockSer
bind(sockSer, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
// 讓服務器Socket開啟監聽,并且設定最大的等待連接數
// 等待連接數(半連接)過大會給服務器造成負載
listen(sockSer, 5);
//開啟監聽執行緒
LPVOID *lp = (LPVOID*)&sockSer;
HANDLE hThread = CreateThread(NULL, 0, ServerListeningConnect, lp, 0, NULL);
// SOCKADDR_IN addrClient;
// int len = sizeof(SOCKADDR);
int cnt = 0;
const int max_connet_cnt = 20;
while (true) {
cnt++;
if (cnt > max_connet_cnt){
printf("超出最大嘗試連接次數,行程即將結束");
system("pause");
//關閉socket庫
closesocket(sockSer);
closesocket(sockCli);
WSACleanup(); //清空加載項
return 0;
}
cout << "正在嘗試連接中,請稍等\n";
//連接接聽請求
listen(sockSer, 5);
//接聽連接請求
sockCli = accept(sockSer, (SOCKADDR*)&addrCli, &naddr);
if (sockCli != INVALID_SOCKET){
//連接成功
cout << "連接成功" << '\n';
strcpy(sendbuf, "hello!");
send(sockCli, sendbuf, sizeof(sendbuf), 0);
// 接收操作
recv(sockCli, recvbuf, 50, 0);
printf("%s\n", recvbuf);
break;
}
}
//全雙工接聽
while (true)
{
char recvbuf[MAX_BUF_SIZE];
for (int i = 1; i <= clientCount; i++)
{
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);
}
}
}
}
//關閉socket庫
closesocket(sockSer);
closesocket(sockCli);
WSACleanup(); //清空加載項
}
客戶端代碼
#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 <functional>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <numeric>
#include <cstring>
#include <climits>
#include <cassert>
#include <complex>
#include <cstdio>
#include <string>
#include <vector>
#include <bitset>
#include <queue>
#include <stack>
#include <cmath>
#include <ctime>
#include <list>
#include <set>
#include <map>
#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];
//全域函式
// 接收執行緒的設定是死回圈不斷得提交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(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;
}
//環境處理
if (LOBYTE(WSAData.wVersion) != 1 || HIBYTE(WSAData.wVersion) != 1) {
WSACleanup(); return;
}
//創建socket(地址描述(AF_INET格式 - ipv4),指定socket型別(使用的是流式套接字即TCP協議), 指定協議
sockCli = socket(AF_INET, SOCK_STREAM, 0);
//客戶端阻化判斷
unsigned long ul = 1;
int ret = ioctlsocket(sockCli, FIONBIO, (unsigned long *)&ul);
if (ret == SOCKET_ERROR) {
printf("非阻塞化失敗!");
return;
}
/* 初始化客戶端的地址包(類似于初始化服務器的地址包)
* 封裝成sockaddr_in 類,俗稱地址包,保存埠號和IP地址
* SOCKADDR_IN addrSrv;
* 將IP地址 INADDR_ANY (0x00000)主機位元組轉換成網路位元組:高位元組在前面
* addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
* 使用局域網中的IP 可以打開cmd,輸入ipconfig進行查詢
*/
/* 函式獲取本地IP的方法如下,
* getLocalIP(localIP, sizeof(localIP) / sizeof(char));
* setColor(COLOR_INFO);
* 上述兩種函式都是需要自己宣告實作
*/
char ip[] = "192.168.81.90";
cout << "吶吶吶,本地IP是" << ip << "該電腦已經開啟!\n";
//setColor(COLOR_NORMAL);
//巡回測驗ip
// inet_addr:將點分十進制的IP 轉換成二進制,然后再轉換成網路位元組
addrCli.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
// 設定為IP協議族
addrCli.sin_family = AF_INET;
// 設定埠號,把主機位元組轉換成網路位元組,根據查詢1024以上隨便寫,最大是 65535
addrCli.sin_port = htons(6000);
//初始化服務器地址
// inet_addr:將點分十進制的IP 轉換成二進制,然后再轉換成網路位元組
addrSer.sin_addr.S_un.S_addr = inet_addr("192.168.81.90");
// 設定為IP協議族
addrSer.sin_family = AF_INET;
// 設定埠號,把主機位元組轉換成網路位元組,根據查詢1024以上隨便寫,最大是 65535
addrSer.sin_port = htons(6000);
//讀取用戶名
char username[50];
printf("請輸入您的用戶名: ");
scanf("%s", username);
const int max_connet_cnt = 20; //最大嘗試連接次數
int cnt = 0;
while (true){
//cnt++;
if (++cnt > max_connet_cnt) {
cout << "連接失敗\n超出最大嘗試連接次數\n";
break;
}
cout << "正在開始嘗試連接,請稍等...\n";
//開始連接
if (connect(sockCli, (SOCKADDR *)&addrSer, sizeof(addrSer)) != SOCKET_ERROR)
{
//連接成功
cout << "恭喜您,連接成功" << '\n';
send(sockCli, username, SEND_SIZE, 0);
//優先將用戶名發送給服務器,可能出現發送失敗的情況,后期記得判斷send回傳值與error的對比
recv(sockCli, recvbuf, sizeof(recvbuf), 0);
cout << recvbuf << '\n'; // OT - cout 可能存在不支持的情況
}
else {
printf("本次嘗試連接失敗\n");
}
}
// 客戶端開啟一個執行緒,負責監聽服務器端
LPVOID *lp = (LPVOID*)&sockCli;
HANDLE hThread = CreateThread(NULL, 0, Client_Receive_Thread, lp, 0, NULL);
// 主執行緒用于輸入,讀取用戶資訊
char input[50];
char msg[MAX_BUF_SIZE];
while (true)
{
printf("請輸入您想要發送的資訊(退出請輸入exit)\n");
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(sockCli, msg, SEND_SIZE, 0);
}
}
//關閉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));
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/241321.html
標籤:其他
上一篇:計算機系統基礎之磁盤存盤
下一篇:戲說資料結構和演算法
