網絡編程(socket): 基於tcp的服務端與客戶端聊天小程序


一、基礎概念

1、網絡架構

Client/Server結構(C/S結構)客戶機和服務器結構。本文的主角。B/S結構(Browser/Server,瀏覽器/服務器模式),WEB瀏覽器是客戶端最主要的應用軟件

2、IP

IP地址是網路通信尋址的主要手段

3、端口(port )

每台計算機有很多個端口。通常是一個進程(運行着的程序)對應一個端口,訪問該主機的某個端口就是訪問對應的進程。有些端口是默認的對應一些進程,像其中80端口分配給WWW服務,21端口分配給FTP服務。通常我們選用1024以上的端口。

4、socket(套接字)

套接字是一個很抽象的概念。可以理解為連接兩個端口之間一條虛擬的線,而端口就是虛擬的接口,或者可以理解為電源線跟插頭的關系。要進行網絡通信,少了它不行。

二、c/s架構

s是服務器(運行了服務端程序的計算機),c是客戶機。c向s發送消息(請求),s接受到消息並處理(響應)。建立連接后s也可以主動向c發送消息,通常是多個c對應一個s。這里我用的只有一個客戶端。

三、tcp通信

1、連接通信

在進行通信前,需要做一個虛連接。即客戶端c想要與服務端s進行通信必須先要與之進行連接。

2、客戶端

客戶端通信很簡單,只要取得與服務端的聯系就可以進行信息的收發。

3、服務端

服務端是一個進程,總是等待着客戶端發來請求,並處理相應的請求。

四、通信過程

不涉及具體調用函數的說明。

1、客戶端設計

第一步、確定要進行連接的服務端的信息(IP、port)

第二步、獲取一個socket,調用相應的函數得到

第三步、用得到的socket與服務端的信息去調用connect與服務端進行連接

第四部、收發消息(send/recv)

第五步、關閉打開的套接字

2、服務端的設計

第一步、確定本機IP和要與程序綁定的端口

第二步、獲取一個監聽用的socket。

第三步、將得到的socket與確定后的端口綁定,調用bind.

第四步、監聽。坐等客戶端的到來(沒來是一個處於阻塞的函數,調用 listen)

第五步、接受客戶端的請求,並建立一個新的套接字來與客戶端通信(accept)

第六步、用建立的套接字與客戶端通信(send/recv)。

第七步、關閉已經打開的套接字,跟文件處理是一樣的。

五、實例演示

1、說明:

首先,程序運行在Windows下有環境依賴,要做跟Windows有關的初始化。包括一些頭文件的包括,需要鏈接的庫等。

其次是程序里面分別在客戶端和服務端創建了一個線程用來收信息,增加程序的體驗感。同樣是windows的原因,調用createThird創建線程時要特別注意線程函數的格式。

也可以選用其他創建線程的函數。

2、客戶端代碼

#include<stdio.h>
#include <stdlib.h>
#include<string.h>
#include <winsock2.h>
#include <windows.h>

/*添加庫的方法:工程->設置->連接->對象/庫模塊 中加入ws2_32.lib*/
#pragma comment(lib,"ws2_32.lib")
#define PORT 8888
#define ADDR "127.0.0.1"
                //函數聲明
DWORD WINAPI ThreadProc(LPVOID lpParam);
            //主函數 
int main(){
    
SOCKET scoket;
SOCKADDR_IN serAddr;
int i=0;
char buffer[1024];
int nRet=0;

WSADATA wsock;    //第一步,很重要。做環境初始化
if(WSAStartup(MAKEWORD(2,2),&wsock)!=0)
{
return 0;
}

                //第二步,獲取套接字
if((scoket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET)
{
    WSACleanup();//獲取套接字失敗后,需要關閉已經初始化的環境
    return 0;
}

                //設置SOCKADDR_IN地址結構
serAddr.sin_family=AF_INET;//ipv4協議簇
serAddr.sin_port=htons(PORT);//設置端口
serAddr.sin_addr.s_addr=inet_addr(ADDR); //設置IP地址

                //第三步,進行連接
if((connect(scoket,(SOCKADDR*)&serAddr,sizeof(serAddr)))==SOCKET_ERROR)
{
    printf("error:%d",WSAGetLastError());
    return 0;
}

                //創建一個線程,用來接收數據。
                //windows里面調用此函數,Linux不一定
CreateThread ( NULL, NULL, ThreadProc,&scoket,0, NULL);

while(1)
{
        memset(buffer,0,sizeof(buffer));//緩存清零
        gets(buffer);
        if(strcmp("exit",buffer)==0)
            goto EXIT;
    if((nRet=send(scoket,buffer,strlen(buffer),0))==SOCKET_ERROR)
    {
        printf("error:%d",WSAGetLastError());
        goto EXIT;
    }
}

            //錯誤處理
EXIT:
closesocket(scoket);/*關閉不使用的套接字,跟文件操作一樣,網絡也是稀缺資源。*/
WSACleanup(); //關閉已經初始化的環境
return 0;

}
            //線程函數,注意返回值和參數類型是固定的
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
    SOCKET *sk=(SOCKET *)lpParam;
char buffer[1024];
while(1)
{
    memset(buffer,0,sizeof(buffer));
    if(recv(*sk,buffer,sizeof(buffer),0)==SOCKET_ERROR)
    {
        printf("error:%d",WSAGetLastError());
        closesocket(*sk);
        WSACleanup();
    return 0;
    }
    if(strcmp(buffer,"exit")==0){
        return 0;
    }
    puts(buffer);
}
}

3、服務端代碼

#include<stdio.h>
#include<string.h>
#include <stdlib.h>
#include <winsock2.h>
#include<windows.h>
/*添加庫的方法:工程->設置->連接->對象/庫模塊 中加入ws2_32.lib*/
#pragma comment(lib,"WS2_32.lib")
#define PORT 8888
#define ADDR "127.0.0.1"

DWORD WINAPI ThreadProc(LPVOID lpParam); 

int main(int argc,char* argv[]){
    
    WSADATA wsock;
    SOCKET listensocket,newconnection;
    SOCKADDR_IN serAddr,cliAddr;
    int     cliAddrLen=sizeof(cliAddr);
    int     nRet=0;
    char     buffer[1024];
    
            //第一步,很重要。做環境初始化
if(WSAStartup(MAKEWORD(2,2),&wsock)!=0)
{
return 0;
}
            //第二步,獲取套接字
if((listensocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET){
    printf("error:%d",WSAGetLastError());
    WSACleanup();//關閉已經初始化的環境
    return 0;
}

printf("設置監聽套接字\n");
            //設置SOCKADDR_IN地址結構
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(PORT);
serAddr.sin_addr.s_addr=inet_addr(ADDR);
//serAddr.sin_addr.S_un.S_addr = INADDR_ANY;

printf("綁定\n");
            //綁定套接字到相應的端口
if(bind(listensocket, (SOCKADDR *)&serAddr,sizeof(serAddr))== SOCKET_ERROR){
    printf("error:%d",WSAGetLastError());
    goto ERR;
}

printf("進入監聽……\n");
            //監聽等待,無連接時處於阻塞狀態
if(listen(listensocket, 5) == SOCKET_ERROR)
{
    printf("error:%d",WSAGetLastError());
    goto ERR;
}

            //錯誤處理
goto NEXT;
ERR:
    closesocket(listensocket);
    WSACleanup();
    return 0;
NEXT:

printf("設置接收連接套接字\n");
            //引用新的套接字與客戶端進行通信
if((newconnection = accept(listensocket, (SOCKADDR *) &cliAddr,
        &cliAddrLen)) == INVALID_SOCKET){
    printf("error:%d",WSAGetLastError());
    goto ERR;
}

            //關閉監聽套接字。也可以循環監聽,進行多並發處理。不關閉
closesocket(listensocket);

printf("收發數據……");
            //創建一個線程,用來接收數據。
            //windows里面調用此函數,Linux不一定
CreateThread ( NULL, NULL, ThreadProc,&newconnection,0, NULL);

            //循環發送數據
while(1)
{
    memset(buffer,0,sizeof(buffer));//清零
    gets(buffer);
    if(strcmp(buffer,"exit")==0){
        goto EXIT;
    }
    if((nRet=send(newconnection,buffer,strlen(buffer),0))==SOCKET_ERROR){
        printf("error:%d",WSAGetLastError());
        goto EXIT;
    }
}

EXIT:
closesocket(newconnection);
WSACleanup();
return 0;

}

            //線程函數,注意返回值和參數類型是固定的
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
    SOCKET *sk=(SOCKET *)lpParam;
char buffer[1024];
while(1)
{
    memset(buffer,0,sizeof(buffer));//清零
    if(recv(*sk,buffer,sizeof(buffer),0)==SOCKET_ERROR)
    {
        printf("error:%d",WSAGetLastError());
        closesocket(*sk);
        WSACleanup();
    return 0;
    }
    if(strcmp(buffer,"exit")==0){
        return 0;
    }
    puts(buffer);
}
}

4、運行結果

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM