大家好,我是KookNut39,在CSDN寫文,分享一些自己認為在學習程序中比較重要的東西,致力于幫助初學者入門,希望可以幫助你進步,最近在更新C/C++方面的知識,感興趣的歡迎關注博主,可以去專欄查看之前的文章,希望未來能和大家共同探討技術,
對于搞計算機的人來說,我們一直處在一個被外行深深誤解的環境下,我還記得上學時候就發生過這樣的對話,這位誤會我的人還是我的好兄弟:
韓:“白哥,今天用空嗎?我電腦壞了,幫我看一下!”
我:“我不太會修電腦啊!你重啟試試”
韓:“啊?你不是計算機專業嗎?”
我:“計算機科學與技術也不是計算機修理專業啊…”
有的計算機專業的大佬可能真的會修計算機,也有的不是計算機的大佬,也會修計算機,反正,我不太會修電腦,我只會…重啟試試???🤣🤣🤣
為了不讓往日悲劇在各位身上重現,各位還是寫個程式給周圍人看一看!告訴他們我們的專業性!那學習了這么久的C語言基礎,不得用C語言來干點啥?寫個簡易的聊天軟體,出去在同學面前裝一下唄!眾所周知,寫代碼如果不是為了在別人面前裝個x,那將變得毫無意義!如果覺得文章不錯,麻煩給個一鍵三連支持一下🤞🤞🤞,您的支持,是我最大的創作動力!
廢話夠多了,趕緊進入正題!
我們要實作一個簡易的聊天系統,那最起碼是兩個人互動的,那么肯定是需要兩個不同的端,也就是最起碼兩個程式,來實作這個簡單的功能,我們就暫且理解為需要一個服務器端,一個客戶端,這樣讓兩個程式完成互動!該代碼在Windows系統進行測驗,如果需要在linux端實作,需要稍作改動!
文章目錄
- 一、服務器
- 二、客戶端
一、服務器
首先來看服務器端,先來搞定幾個頭檔案,不然其中的一些庫函式會沒法呼叫:
#pragma once
#include<WinSock2.h>
#include <stdio.h>
#include <stdlib.h>
#include<Windows.h>//必須在<WinSock2.h>的下面包含,否則編譯不通過
#pragma comment(lib,"WS2_32.lib")//要包含WinSock2.h必須要包這個庫
頭檔案中的這些庫那都是必須要包含的內容,不然之后函式的呼叫就會出現一堆的報錯,下來我們看一下main函式:
//初始化套接字類別庫
//WSAStartup函式用于初始化Ws2_32.dll元件,
//在使用套接字函式之前,一定要初始化Ws2_32.dll元件
WSADATA WsaData = { 0 };
if (WSAStartup(MAKEWORD(2, 2), &WsaData) != 0)
{
return;
}
// 創建監聽套接字
SOCKET ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ListenSocket == INVALID_SOCKET)
{
printf("Failed socket() \n");
return;
}
第一件事情就是初始化套接字類別庫,因為我們需要利用套接字來完成行程間通信,所以類別庫肯定是要首先初始化的,接下來是創建一個監聽套接字,在創建監聽套接字的時候需要注意,socket函式中傳的引數是非常關鍵的:
SOCKET WSAAPI socket(
_In_ int af,//地址家族規范,在這里我們傳的是AF_INET 這是IPv4協議規范
_In_ int type,//這個引數我們傳遞SOCK_STREAM,可靠的資料流傳輸,因為TCP協議
_In_ int protocol//傳輸控制協議,用的TCP
);
這個函式的三個引數在TCP/IP通信中,基本是固定搭配套餐!當我們把監聽套接字創建出來之后,需要將接聽套接字與埠系結:
// 填充sockaddr_in結構
struct sockaddr_in ServerAddress;
ServerAddress.sin_family = AF_INET;//Ipv4協議家族
ServerAddress.sin_port = htons(4567); //埠號
ServerAddress.sin_addr.S_un.S_addr = INADDR_ANY;//客戶端是本地地址
// 系結套接字
if (bind(ListenSocket, (LPSOCKADDR)&ServerAddress, sizeof(ServerAddress)) == SOCKET_ERROR)
{
printf("Failed bind() \n");
return;
}
上面的代碼中,有一個結構體sockaddr_in其中包含了三個成員,有地址協議家族、監聽埠號和監聽的地址,其中埠號是隨便設定的,只要在埠號范圍之內,不要和知名埠號重復就行,我隨便寫了個4567,保證客戶端也連接到這個埠就行!
bind函式是系結套接字和sockaddr_in結構體,為了讓這個套接字可以在該埠和地址協議規范下完成監聽,bind函式將本地地址與套接字關聯起來,
服務器端完成了套接字埠系結之后,就要開始監聽,listen函式將套接字置于偵聽傳入連接的狀態,可以設定最大的連接數,在這里我隨便設定了2,
// 進入監聽模式 監聽佇列 最大連接數設定為 2
if (listen(ListenSocket, 2) == SOCKET_ERROR)
{
printf("Failed listen() \n");
return;
}
那監聽上線之后,就等著客戶端的連接過來,需要一個叫做accept的函式來接受客戶端的連接,accept函式允許對套接字的傳入連接嘗試,在這里設計算是偷了個懶,本應該弄一個回圈,因為這是嘗試連接,如果連接達到上限,就不允許其它的客戶端接入了,應該不斷嘗試連接,但是這里我們主要為了講一下實作原理,用于間單的測驗還是沒問題的
//用于接受客戶端連接的IP地址等資訊
struct sockaddr_in ClientAddress;
int AddressLength = sizeof(ClientAddress);//計算這個長度在accept處使用
SOCKET ClientSocket;
printf("等待客戶端連接:\n");
// 接受一個新連接
ClientSocket = accept(ListenSocket, (SOCKADDR*)&ClientAddress, &AddressLength);
if (ClientSocket == INVALID_SOCKET)
{
printf("Failed accept()");
}
客戶端和服務器連接成功之后,我們創建一個執行緒,在執行緒創建程序中,把客戶端的Socket當作引數傳遞給執行緒,這個執行緒用于給客戶端發送訊息:
printf("接收到連接:%s \r\n", inet_ntoa(ClientAddress.sin_addr));
HANDLE ThreadHandle = CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)ThreadProcedure,
&ClientSocket,
0,
NULL);
if (ThreadHandle == NULL)
{
return 0;
}
在這個回呼執行緒執行函式中,用于和客戶端通信,用gets來讀取資料,遇到回車讀取結束,然后只要保持連接,就可以一直給客戶端發送訊息,如果想斷開連接,輸入Over即可,
//相當于一個發送訊息模塊
DWORD WINAPI ThreadProcedure(LPVOID Parameter)
{
SOCKET ClientSocket;
char BufferData[260];//最大發送的字符數
ClientSocket = *(SOCKET*)Parameter;
printf("You can speak now:\n");
while (1)
{
memset(BufferData, 0, sizeof(BufferData));
gets(BufferData);
// 向客戶端發送資料
send(ClientSocket, BufferData, strlen(BufferData), 0);
if (!strncmp(BufferData, "Over", strlen("Over")))
{
// 關閉同客戶端的連接 退出程式
closesocket(ClientSocket);
exit(0);
}
}
return 0;
}
在異步執行緒可以發送訊息的同時,主執行緒也沒閑著,它在接收客戶端的資料發送,也是在一個while回圈中,一直接受者來自客戶端的訊息,直到客戶端發出Over指示,斷開連接:
//用于接收資料
char BufferData[260];
while (TRUE)
{
memset(BufferData, 0, sizeof(BufferData));
recv(ClientSocket, BufferData, sizeof(BufferData), 0);
if (!strncmp(BufferData, "Over", strlen("Over")))
{
CloseHandle(ThreadHandle);
ThreadHandle = NULL;
break;
}
printf("Client Said: %s\n", BufferData);
}
到這里,一個簡單的服務器端就搞定了!!!接下來我們看一下客戶端的實作吧:
二、客戶端
客戶端的代碼實作邏輯其實和服務器端是相當接近的,我們需要包含的頭檔案也沒有變化:
#pragma once
#include<WinSock2.h>
#include <stdio.h>
#include <stdlib.h>
#include<Windows.h>//必須在<WinSock2.h>的下面包含,否則編譯不通過
#pragma comment(lib,"WS2_32.lib")//要包含WinSock2.h必須要包這個庫
這些頭檔案都是必須包含的,在之前就已經說過了,因為實作邏輯很接近,所以我就找那些不太一樣的地方來給大家解釋一下:
一上來那肯定是main函式了,里面還是一樣,初始化類別庫,創建套接字:
//初始化套接字類別庫
//WSAStartup函式用于初始化Ws2_32.dll元件,在使用套接字函式之前,一定要初始化Ws2_32.dll元件
WSADATA v1 = { 0 };
if (WSAStartup(MAKEWORD(2, 2), &v1) != 0)
{
return;
}
// 創建套接字
SOCKET CommunicateSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (CommunicateSocket == INVALID_SOCKET)
{
printf(" Failed socket() \n");
return;
}
然后我們需要宣告并且給sockaddr_in結構體賦值,這里有所不同,對于地址協議家族和埠號來說是一樣的,尤其埠號,肯定要和服務器保持一致,然后我們講連接的地址寫為“127.0.0.1”,這是連接到本地的IP地址,在本機方便測驗:
// 填寫遠程地址資訊
struct sockaddr_in ServerAddress;
ServerAddress.sin_family = AF_INET;
ServerAddress.sin_port = htons(4567);
//此處直接使用127.0.0.1即可 就是連接到本機
ServerAddress.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
初始化結束之后,我們使用connect將客戶端的套接字通過這個IP和埠來和服務器進行連接,connect函式建立到指定套接字的連接:
if (connect(CommunicateSocket, (SOCKADDR*)&ServerAddress, sizeof(ServerAddress)) == SOCKET_ERROR)
{
printf(" Failed connect() \n");
return;
}
if (CommunicateSocket == INVALID_SOCKET)
{
printf("Failed accept()");
}
一旦連接成功,繼續創建執行緒,這個執行緒傳的是當前與服務器連接起來的CommunicateSocket,這個套接字就是客戶端和服務器交流的橋梁:
printf("連接成功!!\r\n");
HANDLE ThreadHandle = CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)ThreadProcedure,
&CommunicateSocket,
0,
NULL);
if (ThreadHandle == NULL)
{
return 0;
}
創建異步執行緒還是一樣的,將當前傳入的套接字,用于給服務器發送訊息,發送Over來結束當前會話:
//向服務器發送訊息
DWORD WINAPI ThreadProcedure(LPVOID Parameter)
{
SOCKET ServerSocket;
char BufferData[260];
ServerSocket = *(SOCKET*)Parameter;
printf("You can speak now:\n");
while (1)
{
memset(BufferData, 0, sizeof(BufferData));
gets(BufferData);
// 向服務器發送資料
send(ServerSocket, BufferData, strlen(BufferData), 0);
if (!strncmp(BufferData, "Over", strlen("Over")))
{
// 關閉同服務器的連接 退出程式
closesocket(ServerSocket);
exit(0);
}
}
return 0;
}
那main函式中的主執行緒肯定還是接受來自服務器端的訊息,除非遇到Over指令,來結束對話:
//接受來自服務器的訊息
char BufferData[260];
while (TRUE)
{
memset(BufferData, 0, sizeof(BufferData));
recv(CommunicateSocket, BufferData, sizeof(BufferData), 0);
if (!strncmp(BufferData, "Over", strlen("Over")))
{
CloseHandle(ThreadHandle);
ThreadHandle = NULL;
break;
}
printf("Server Said: %s\n", BufferData);
}
到這里,實作就基本結束了,記得代碼中斷開連接之后,最后關閉套接字,代碼記得首先啟動服務器,這樣才能達到監聽的效果,讓客戶端順利連接,我們來用動態圖演示一下效果:

如果覺得文章不錯,麻煩給個點贊+評論+收藏支持一下🤞🤞🤞,代碼實作其實在文章中已經夠詳細了,但是如果有需要原始碼的,可以找我要,感謝您的閱讀!
今日份與君共勉:“人生如逆旅,我亦是行人”

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/286955.html
標籤:其他
