亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C++服務(wù)器和客戶端交互的項(xiàng)目實(shí)踐

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

網(wǎng)絡(luò)與通信Socket

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

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

Socket通信模型

telnet ipxx 進(jìn)行主機(jī)間通信。

一個(gè)簡(jiǎn)單的服務(wù)器和客戶端通信程序,服務(wù)器端代碼:

#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);
	//重置結(jié)構(gòu)體的內(nèi)存空間
    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中,表示進(jìn)程x間網(wǎng)絡(luò)通信的特殊文件類型。本質(zhì)上為內(nèi)核借助緩沖區(qū)形成的為文件。可以使用文件描述符引用套接字。Linux系統(tǒng)將其封裝成文件的目的是為了統(tǒng)一接口,使讀寫套接字和讀寫文件操作一致。區(qū)別在于文件主要因用于本地持久化數(shù)據(jù)的讀寫,而套接字多應(yīng)用于網(wǎng)絡(luò)進(jìn)程間數(shù)據(jù)的傳遞。

在TCP/IP協(xié)議中,IP地址-TCP或UDP端口號(hào) 唯一標(biāo)識(shí)網(wǎng)絡(luò)通訊中的一個(gè)進(jìn)程。IP地址+端口號(hào) 就對(duì)應(yīng)一個(gè)socket。與建立連接的兩個(gè)進(jìn)程各有一個(gè)socket來標(biāo)識(shí),那么這兩個(gè)socket組成的socket pair就唯一標(biāo)識(shí)一個(gè)連接。因此可以用Socket來描述網(wǎng)絡(luò)連接的一對(duì)一關(guān)系。

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

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

四次揮手結(jié)束客戶端和服務(wù)器端的通訊。

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

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

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

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

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

//頭文件,庫函數(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位長(zhǎng)整型,s表示16位短整型。

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

SocketAddr詳解

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

SocketAddress結(jié)構(gòu)圖

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結(jié)構(gòu)體表示,包括16位端口號(hào)和32位IP地址,但是sock API的實(shí)現(xiàn)早于ANSI C標(biāo)準(zhǔn)化,那時(shí)還沒有void *類型,因此這些像bind 、accept函數(shù)的參數(shù)都用struct sockaddr *類型表示,在傳遞參數(shù)之前要強(qiáng)制類型轉(zhuǎn)換一下,例如:

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

IP地址轉(zhuǎn)化

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

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

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

Socket編程

socket函數(shù)

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

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

bind函數(shù)

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

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

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

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

Listen函數(shù)

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

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

查看系統(tǒng)默認(rèn)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地址和端口號(hào)
addrlen:
	傳入傳出參數(shù)(值-結(jié)果),傳入sizeof(addr)大小,函數(shù)返回時(shí)返回真正接受到地址結(jié)構(gòu)體的大小
return:
	返回一個(gè)新的socket文件描述符,用于和客戶端通信,失敗返回-1,并設(shè)置errno

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

服務(wù)器端代碼結(jié)構(gòu)案例:

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

整個(gè)結(jié)構(gòu)是一個(gè)while死循環(huán),每次循環(huán)處理一個(gè)客戶端連接,由于cliaddr_len是一個(gè)傳入傳出參數(shù),每次調(diào)用accept()之前應(yīng)該重新賦初值。accept()的參數(shù)listenfs是先前監(jiān)聽的文件描述符,而accept()的返回值是另外一個(gè)文件描述符connfd,之后與客戶端之間就是通過或者connfd通訊,最后關(guān)閉connfd斷開連接,而不關(guān)閉listenfd,再次回到循環(huán)開頭listenfd仍然用作accept參數(shù)。accept()成功返回一個(gè)文件描述符,出錯(cuò)返回-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ù),指定服務(wù)器端地址信息,含IP地址和端口號(hào)
addrlen:
	傳入?yún)?shù),指定服務(wù)器段地址信息,含IP地址和端口信息
return:
	成功返回0,失敗返回-1,并設(shè)置errno

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

出錯(cuò)處理函數(shù)

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

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

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

相關(guān)文章

最新評(píng)論