欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++服務器和客戶端交互的項目實踐

 更新時間:2023年07月11日 14:48:19   作者:Trouble..  
本文主要介紹了C++服務器和客戶端交互的項目實踐,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

網(wǎng)絡與通信Socket

Socket通信三要素:通信的目的地址、使用的端口號(http 80 / smtp 25)、使用的傳輸協(xié)議(TCP、UDP)。

nslookup xx 可以查詢xx網(wǎng)址的IP地址。

Socket通信模型

telnet ipxx 進行主機間通信。

一個簡單的服務器和客戶端通信程序,服務器端代碼:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ctype.h>
#include <arpa/inet.h>
#define SERVER_PORT 666
int main(void)
{
    int sock;
    struct sockaddr_in server_addr;
    sock = socket(AF_INET, SOCK_STREAM, 0);
    // printf("wait \n");
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERVER_PORT);
    bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
    listen(sock, 128);
    printf("wait client connect\n");
    int done = 1;
    while (done)
    {
        struct sockaddr_in client;
        int client_sock, len, i;
        char client_ip[64];
        char buf[256];
        socklen_t client_addr_len;
        client_addr_len = sizeof(client);
        client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);
        printf("client ip: %s \t port is : %d \n", inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)), ntohs(client.sin_port));
        len = read(client_sock, buf, sizeof(buf) - 1);
        buf[len] = '\0';
        printf("receive[%d]:%s\n", len, buf);
        len = write(client_sock, buf, len);
        printf("finish. len:%d\n", len);
        close(client_sock);
    }
    close(sock);
    return 0;
}

客戶端代碼:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SERVER_PORT 666
#define SERVER_IP  "127.0.0.1"
int main(int argc, char *argv[]){
    int sockfd;
    char *message;
    struct sockaddr_in servaddr;
    int n;
    char buf[64];
    if(argc != 2){
        fputs("Usage: ./echo_client message \n", stderr);
        exit(1);
    }
    message = argv[1];
    printf("message: %s\n", message);
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
	//重置結構體的內存空間
    memset(&servaddr, '\0', sizeof(struct sockaddr_in));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr);
    servaddr.sin_port = htons(SERVER_PORT);
    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    write(sockfd, message, strlen(message));
    n = read(sockfd, buf, sizeof(buf)-1);
    if(n>0){
        buf[n]='\0';
        printf("receive: %s\n", buf);
    }else {
        perror("error!!!");
    }
    printf("finished.\n");
    close(sockfd);
    return 0;
}

Socket概念

socket(套接字)的中文意思為插座,socket一般用整型表示,Linux中,表示進程x間網(wǎng)絡通信的特殊文件類型。本質上為內核借助緩沖區(qū)形成的為文件。可以使用文件描述符引用套接字。Linux系統(tǒng)將其封裝成文件的目的是為了統(tǒng)一接口,使讀寫套接字和讀寫文件操作一致。區(qū)別在于文件主要因用于本地持久化數(shù)據(jù)的讀寫,而套接字多應用于網(wǎng)絡進程間數(shù)據(jù)的傳遞。

在TCP/IP協(xié)議中,IP地址-TCP或UDP端口號 唯一標識網(wǎng)絡通訊中的一個進程。IP地址+端口號 就對應一個socket。與建立連接的兩個進程各有一個socket來標識,那么這兩個socket組成的socket pair就唯一標識一個連接。因此可以用Socket來描述網(wǎng)絡連接的一對一關系。

網(wǎng)絡通訊中,套接字一定是成對出現(xiàn)的。一段的發(fā)送緩沖區(qū)對應另一端的接收緩沖區(qū)。使用同一個文件描述符發(fā)送緩沖區(qū)和接收緩沖區(qū)。

服務器和客戶端之間的通訊是全雙工的,可以互相讀寫,采用同步和異步的方式進行交互。

四次揮手結束客戶端和服務器端的通訊。

網(wǎng)絡字節(jié)序

  • 大端字節(jié)序-低地址高字節(jié),高地址低字節(jié)
  • 小端字節(jié)序-低地址低字節(jié),高地址高字節(jié)。

內存中的多字節(jié)數(shù)據(jù)相對于內存地址、磁盤文件中的多字節(jié)數(shù)據(jù)相對于文件中的偏移地址,網(wǎng)絡數(shù)據(jù)流都有大端和小端之分。發(fā)送主機通常將發(fā)送緩沖區(qū)中的數(shù)據(jù)按內存地址從低到高的順序發(fā)出,接收主機把從網(wǎng)絡上接到的字節(jié)一次保存在接受緩沖區(qū)中,也是按照內存地址從低到高的順序保存。因此,網(wǎng)絡數(shù)據(jù)流的地址應該這樣規(guī)定:先發(fā)出的數(shù)據(jù)是低地址,后發(fā)出的數(shù)據(jù)是高地址。

TCP/IP協(xié)議規(guī)定,網(wǎng)絡數(shù)據(jù)流應采用大端字節(jié)序,既低地址高字節(jié)。

32位IP地址也要考慮網(wǎng)絡字節(jié)序和主機字節(jié)序的問題。C/C++中采用一下庫函數(shù)進行網(wǎng)絡字節(jié)序和主機字節(jié)序的轉換。

//頭文件,庫函數(shù)
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

h表示host,n表示network,l表示32位長整型,s表示16位短整型。

如果主機是小端字節(jié)序,這些函數(shù)將參數(shù)做相應的大小端轉化然后返回,如果主機是大端字節(jié)序,這些函數(shù)不做轉換,將參數(shù)原封不動地返回。

SocketAddr詳解

很多網(wǎng)絡編程函數(shù)誕生早于IPv4協(xié)議,那時候都使用的是sockaddr結構體,為了向前兼容,現(xiàn)在sockaddr退化成了(void *)的作用,傳遞一個地址給函數(shù),至于這個函數(shù)是sockaddr_in還是其他的,由地址族確定,然后函數(shù)內部再強制類型轉化為所需的地址類型。

SocketAddress結構圖

struct sockaddr {
	sa_family_t sa_family;/* address family, AF_xxx AF_INET(IPV4) AF_INET(IPV6)*/
	char sa_data[14];     /* 14 bytes of protocol address */
};
 struct sockaddr_in {
     sa_family_t    sin_family; /* address family: AF_INET */
     in_port_t      sin_port;   /* port in network byte order */
     struct in_addr sin_addr;   /* internet address */
 };
 /* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

IPv4的地址格式定義在netinet/in.h中,IPv4地址用sockaddr_in結構體表示,包括16位端口號和32位IP地址,但是sock API的實現(xiàn)早于ANSI C標準化,那時還沒有void *類型,因此這些像bind 、accept函數(shù)的參數(shù)都用struct sockaddr *類型表示,在傳遞參數(shù)之前要強制類型轉換一下,例如:

struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));/* initialize servaddr */

IP地址轉化

#include <arpa/inet.h>
//將字符串的IP轉化為網(wǎng)絡的整型IP
int inet_pton(int af, const char *src, void *dst);
//將網(wǎng)絡字節(jié)序的IP轉化為字符串的IP
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

af 取值可選為 AF_INETAF_INET6 ,即和 ipv4 和ipv6對應支持IPv4和IPv6假設主機地址位2.3.4.5,其中2表示低位,5表示高位,則大端字節(jié)序的結果為5040302(使用inet_pton(AF_INET,"2.3.4.5",&s_add)進行轉化),小端字節(jié)序為2030405(使用ntohl(s_addr)進行轉化)。

ipconfig /all查看主機的網(wǎng)絡地址。

Socket編程

socket函數(shù)

//頭文件
#include<sys/types.h>
#include<sys/socket.h>
int socket(int doamin,int type,int protocol);
domain:
	AF_INET 這是大多數(shù)用來產生socket的協(xié)議,使用TCP或UDP來傳輸,用IPV4的地址。
	AF_INET6 和AF_INET類似,不過是用來IPV6的地址。
	AF_UNIX 本地協(xié)議,使用在Unix和Linux系統(tǒng)上,一般都是當客戶端和服務器在同一臺主機及同時使用的的協(xié)議。
type:
	SOCK_STREAM 這個協(xié)議是按照順序的、可靠的、數(shù)據(jù)完成的基于字節(jié)流的連接。這是一個使用最多的socket類型,用于TCP進行傳輸?shù)摹?
	SOCK_DGRAM 這個協(xié)議是無連接的、固定長度的傳輸調用。該協(xié)議是不可靠的,使用UDP來進行連接。
	SOCK_SEQPACKET 該協(xié)議是雙線路的,可靠的鏈接,發(fā)送固定長度的數(shù)據(jù)包進行傳輸。必須把這個包完整接受才能進行讀取。
	SOCK_RAW socket類型提供單一的網(wǎng)絡訪問,這個socket類型使用ICMP公共協(xié)議。(ping、traceroute使用該協(xié)議)
	SOCK_RDM 這個類型是很少使用的,在大部分操作系統(tǒng)上沒有實現(xiàn),它是提供給數(shù)據(jù)鏈路層使用,不保證數(shù)據(jù)包的順序。
protocol:
	傳0表示使用默認協(xié)議
return:
	成功:返回只想新創(chuàng)建的socket的文件描述符。失?。悍祷?1,并設置errno。

socket()打開一個網(wǎng)絡通訊端口,如果成功的話,就像open()一樣返回一個文件描述符,應用程序可以向讀寫文件一樣用read/write在網(wǎng)絡上收發(fā)數(shù)據(jù),如果socket()調用出錯則返回-1。

bind函數(shù)

服務器將程序所監(jiān)聽的網(wǎng)絡地址和端口號通常是固定不變的,客戶端程序得知服務器程序的地址和端口號就可以向服務器發(fā)起連接,因此服務器需要調用bind進行綁定。

//頭文件
#include<sys/type.h>
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t addren);
sockfd:
	socket文件描述符
addr:
	購找出IP地址加端口號
addrlen:
	sizeof(addr)長度
return:
	成功返回0。失敗返回-1,設置errno。

bind()的作用是將參數(shù)sockfdaddr綁定在一起,使sockfd這個用于網(wǎng)絡通訊的文件描述符監(jiān)聽addr所描述的地址和端口號。struct sockaddr *是一個通用指針類型,addr參數(shù)實際上可以接受多種協(xié)議的sockaddr結構體,而它們的長度各不相同,所以需要第三個參數(shù)addrlen指定結構體的長度。

struct sockaddr_in servaddr;
//結構體清空很重要
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);

Listen函數(shù)

典型的服務器程序可以同時服務與多個客戶端,當有客戶端發(fā)起連接時,服務器調用的accept()函數(shù)返回并接受這個連接,如果有大量的可u段發(fā)起連接而服務器來不及處理,桑威accept的客戶端就處于這個連接等待狀態(tài),listen()僧名sockfd處于監(jiān)聽狀態(tài),如果接受到更多的連接請求就忽略,listen()成功返回0,失敗返回-1.

//頭文件
#include<sys/types.h>
#include<sys/socket.h>
int listen(int sockfd,int backlog);
sockfd:
	socket文件描述符
backlog:
	在Linux系統(tǒng)中,它是指排隊等待建立3次握手隊列長度

查看系統(tǒng)默認backlog

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

accept函數(shù)

//頭文件
#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd,struct aockaddr *addr,socklen_t *addrlen);
sockdf:
	socket文件描述符
addr:
	傳出的參數(shù),返回鏈接客戶端地址信息,含IP地址和端口號
addrlen:
	傳入傳出參數(shù)(值-結果),傳入sizeof(addr)大小,函數(shù)返回時返回真正接受到地址結構體的大小
return:
	返回一個新的socket文件描述符,用于和客戶端通信,失敗返回-1,并設置errno

三次握手以后,服務器調用accept()接受連接,如果服務器調用accept()時還沒有客戶端的連接請求,就阻塞等待直到有客戶端連接上來。addr是一個傳出參數(shù),accept()返回時傳出客戶端的地址和端口號。

服務器端代碼結構案例:

while (1) {
	cliaddr_len = sizeof(cliaddr);
	//如果沒有客戶端連接就會一直堵塞在這個代碼上,不會往下進行執(zhí)行
	connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
	n = read(connfd, buf, MAXLINE);
	.......
	close(connfd);
}

整個結構是一個while死循環(huán),每次循環(huán)處理一個客戶端連接,由于cliaddr_len是一個傳入傳出參數(shù),每次調用accept()之前應該重新賦初值。accept()的參數(shù)listenfs是先前監(jiān)聽的文件描述符,而accept()的返回值是另外一個文件描述符connfd,之后與客戶端之間就是通過或者connfd通訊,最后關閉connfd斷開連接,而不關閉listenfd,再次回到循環(huán)開頭listenfd仍然用作accept參數(shù)。accept()成功返回一個文件描述符,出錯返回-1。

connect函數(shù)

//頭文件
#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
sockdf:
	socket文件描述符
addr:
	傳入?yún)?shù),指定服務器端地址信息,含IP地址和端口號
addrlen:
	傳入?yún)?shù),指定服務器段地址信息,含IP地址和端口信息
return:
	成功返回0,失敗返回-1,并設置errno

客戶端需要調用connect()連接服務器,connectbind的參數(shù)一致,區(qū)別在于bind的參數(shù)是自己的地址,而connect的參數(shù)是對方的地址。

出錯處理函數(shù)

系統(tǒng)調用不能保證每次執(zhí)行都成功,應該盡快獲得程序故障信息。

//頭文件
#include<errno.h>
#include<string.h>
char *strerror(int errnum)
errnum:
	傳入?yún)?shù),錯誤編號的值,一般去errno的值
return:
	錯誤原因
#include<stdio.h>
#include<errno.h>
void perror(const char *s);
s:
	傳入?yún)?shù),自定義描述
return:
	無
向標準出錯stdeer輸出出錯原因(控制臺打?。?/pre>

到此這篇關于C++服務器和客戶端交互的項目實踐的文章就介紹到這了,更多相關C++服務器和客戶端交互內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • C語言鏈表實現(xiàn)簡易通訊錄

    C語言鏈表實現(xiàn)簡易通訊錄

    這篇文章主要為大家詳細介紹了C語言鏈表實現(xiàn)簡易通訊錄,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • C++實現(xiàn)獲取時間戳和計算運行時長

    C++實現(xiàn)獲取時間戳和計算運行時長

    這篇文章主要為大家詳細介紹了如何使用C++實現(xiàn)獲取時間戳和計算運行時長功能,文中的示例代碼講解詳細,有需要的小伙伴可以參考一下
    2024-12-12
  • 詳解C語言基礎的類型轉換

    詳解C語言基礎的類型轉換

    這篇文章主要為大家介紹了C語言基礎的類型轉換,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-11-11
  • C++下程序運行時間的四種常用計時方法總結

    C++下程序運行時間的四種常用計時方法總結

    這篇文章主要介紹了C++下程序運行時間的四種常用計時方法,介紹了幾種常用的計時方法,包括低精度的clock()和GetTickCount(),以及高精度的gettimeofday()和QueryPerformanceCounter(),需要的朋友可以參考下
    2024-09-09
  • c語言10個經(jīng)典小程序

    c語言10個經(jīng)典小程序

    c語言的經(jīng)典程序,學習c語言的初學者可以參考下
    2013-01-01
  • UE4 Unlua 調用異步藍圖節(jié)點AIMoveTo函數(shù)示例詳解

    UE4 Unlua 調用異步藍圖節(jié)點AIMoveTo函數(shù)示例詳解

    這篇文章主要為大家介紹了UE4 Unlua 調用AIMoveTo函數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-09-09
  • C語言修煉之路初識指針陰陽竅?地址還歸大道真下篇

    C語言修煉之路初識指針陰陽竅?地址還歸大道真下篇

    指針是指向另一個變量的變量。意思是一個指針保存的是另一個變量的內存地址。換句話說,指針保存的并不是普通意義上的數(shù)值,而是另一個變量的地址值。一個指針保存了另一個變量的地址值,就說這個指針“指向”了那個變量
    2022-02-02
  • C++中vector的清理回收的方式

    C++中vector的清理回收的方式

    這篇文章主要介紹了C++中vector的清理回收的方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • C++基于文件流與armadillo讀取mnist示例詳解

    C++基于文件流與armadillo讀取mnist示例詳解

    這篇文章主要給大家介紹了關于C++基于文件流與armadillo讀取mnist的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-05-05
  • C語言指針變量作為函數(shù)參數(shù)的實現(xiàn)步驟詳解

    C語言指針變量作為函數(shù)參數(shù)的實現(xiàn)步驟詳解

    這篇文章主要介紹了C語言指針變量作為函數(shù)參數(shù)的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2023-02-02

最新評論