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

C/C++?Linux?Socket網(wǎng)絡(luò)編程流程分析

 更新時(shí)間:2023年02月06日 14:55:01   作者:cpp_learners  
這篇文章主要介紹了C/C++?Linux?Socket網(wǎng)絡(luò)編程,Linux環(huán)境中的C/C++?socket?與Window環(huán)境中的C/C++?socket類(lèi)似,本文所記錄的是TCP協(xié)議的socket編程,圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下

之前已經(jīng)學(xué)習(xí)了QT的socket編程 和 C/C++在window環(huán)境的socket編程,現(xiàn)在再來(lái)學(xué)習(xí)一波C/C++在Linux環(huán)境下的socket編程,為以后學(xué)習(xí)C++ Linux 服務(wù)器開(kāi)發(fā)做準(zhǔn)備。

一、Socket簡(jiǎn)介

既然是socket,那必然有TCP 和 UDP之分,本文所記錄的是TCP協(xié)議的socket編程。

socket編程分為T(mén)CP和UDP兩個(gè)模塊,其中TCP是可靠的、安全的,常用于發(fā)送文件等,而UDP是不可靠的、不安全的,常用作視頻通話(huà)等。

如下圖

 Socket通信3要素:

  • 通信的目的地址;
  • 使用的端口號(hào);
  • 使用的傳輸層協(xié)議(如TCP、UDP)

Socket通信模型

 Socket被稱(chēng)之為套接字。

在Linux環(huán)境中,Socket編程都是以偽文件的形式運(yùn)行著;既然是文件,我們可以使用文件描述符引用套接字。(Linux一切皆文件)

Linux系統(tǒng)將其封裝成文件的目的是為了統(tǒng)一接口,使得讀寫(xiě)套接字和讀寫(xiě)文件的操作一致。區(qū)別是文件主要應(yīng)用于本地持久化數(shù)據(jù)的讀寫(xiě),而套接字多應(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來(lái)標(biāo)識(shí),那么這兩個(gè)socket組成的socket pair就唯一標(biāo)識(shí)一個(gè)連接。因此可以用Socket來(lái)描述網(wǎng)絡(luò)連接的一對(duì)一關(guān)系。

套接字通信原理如下圖所示:

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

Socket 通信創(chuàng)建流程圖

二、Socket編程基礎(chǔ)

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

在計(jì)算機(jī)世界里,有兩種字節(jié)序:

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

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

內(nèi)存中的多字節(jié)數(shù)據(jù)相對(duì)于內(nèi)存地址有大端和小端之分,磁盤(pán)文件中的多字節(jié)數(shù)據(jù)相對(duì)于文件中的偏移地址也有大端小端之分,網(wǎng)絡(luò)數(shù)據(jù)流同樣有大端小端之分。

網(wǎng)絡(luò)數(shù)據(jù)流的地址有這樣規(guī)定:先發(fā)出的數(shù)據(jù)是低地址,后發(fā)出的數(shù)據(jù)是高地址。

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

所以,我們?cè)诖a中必須要將ip地址和端口號(hào)做相應(yīng)的轉(zhuǎn)換,轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序才可以進(jìn)行通訊。

大多數(shù)使用 htonl 和 htons 。

為什么需要轉(zhuǎn)換呢?

假設(shè)本地主機(jī)使用的是小端字節(jié)序,而對(duì)方主機(jī)使用的是大端字節(jié)序;你發(fā)送數(shù)據(jù)過(guò)去的地址順序是:0x06b3,而對(duì)方接受到的卻是:0xb306;這樣數(shù)據(jù)就亂了,所以需要進(jìn)行轉(zhuǎn)換!

需要通過(guò)以下接口進(jìn)行轉(zhuǎn)換:

#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)整數(shù),s表示16位短整數(shù)。

l 結(jié)尾的函數(shù)用于ip地址轉(zhuǎn)換,s 結(jié)尾的函數(shù)用于端口號(hào)的轉(zhuǎn)換。

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

2. sockaddr數(shù)據(jù)結(jié)構(gòu)

我們?cè)谑褂胹ocket中,需要使用結(jié)構(gòu)體sockaddr_in將IP地址和端口號(hào)等保存,然后用于綁定socket;

但進(jìn)行綁定時(shí),我們卻要將結(jié)構(gòu)體sockaddr_in強(qiáng)制類(lèi)型轉(zhuǎn)換為結(jié)構(gòu)體sockaddr,這是為什么呢?

由于歷史原因,一開(kāi)始是沒(méi)有結(jié)構(gòu)體sockaddr_in,只有結(jié)構(gòu)體sockaddr。

后來(lái)為了適配ipv4的到來(lái),將結(jié)構(gòu)體sockaddr細(xì)化為結(jié)構(gòu)體sockaddr_in,如上圖。

兩個(gè)結(jié)構(gòu)體如下:

struct sockaddr {
	sa_family_t sa_family; 		/* address family, AF_xxx */
	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í)還沒(méi)有void *類(lèi)型,因此這些像bind 、accept函數(shù)的參數(shù)都用struct sockaddr *類(lèi)型表示,在傳遞參數(shù)之前要強(qiáng)制類(lèi)型轉(zhuǎn)換一下,

例如:

struct sockaddr_in servaddr;

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

3. IP地址轉(zhuǎn)換函數(shù)

上面網(wǎng)絡(luò)字節(jié)序中我們使用了htonl 和 ntohl 兩個(gè)函數(shù)進(jìn)行ip地址的轉(zhuǎn)換,但只能將uint32_t類(lèi)型的地址進(jìn)行轉(zhuǎn)換,例如:INADDR_ANY ==> 0.0.0.0

但是實(shí)際項(xiàng)目中我們?cè)O(shè)置ip地址大多數(shù)都是字符串,所以得使用特定的函數(shù)去進(jìn)行轉(zhuǎn)換。

#include <arpa/inet.h>

int inet_pton (int af, const char *src, void *dst);        // “本地ip轉(zhuǎn)換為網(wǎng)絡(luò)ip”

const char *inet_ntop (int af, const void *src, char *dst, socklen_t size);        // “網(wǎng)絡(luò)ip轉(zhuǎn)換為本地ip”

af 取值可選為 AF_INET 和 AF_INET6 ,即和 ipv4 和ipv6對(duì)應(yīng)支持IPv4和IPv6;

src 是轉(zhuǎn)換前ip,dst 是轉(zhuǎn)換后ip;

其中inet_pton和inet_ntop不僅可以轉(zhuǎn)換IPv4的in_addr,還可以轉(zhuǎn)換IPv6的in6_addr。

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
 
int main(int argc, char *argv[]) {
	
	char ip[] = "6.7.8.9";
	char server_ip[64];
	
	struct sockaddr_in server_addr;
	
	inet_pton(AF_INET, ip, &server_addr.sin_addr.s_addr);
	
	printf("s_addr : %x\n", server_addr.sin_addr.s_addr);
	
	printf("s_addr from net to host : %x \n", ntohl(server_addr.sin_addr.s_addr));
	
	inet_ntop(AF_INET, &server_addr.sin_addr.s_addr, server_ip, sizeof(server_ip));
	
	printf("server_ip : %s \n", server_ip);
	
	printf("INADDR_ANY: %d \n", INADDR_ANY);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	inet_ntop(AF_INET, &server_addr.sin_addr.s_addr, server_ip, sizeof(server_ip));
	printf("INADDR_ANY ip : %s\n", server_ip);
	
	return 0;
}

 運(yùn)行結(jié)果:

s_addr : 9080706
s_addr from net to host : 6070809
server_ip : 6.7.8.9
INADDR_ANY: 0
INADDR_ANY ip : 0.0.0.0

ip地址:6.7.8.9

因?yàn)榫W(wǎng)絡(luò)上使用的是大端字節(jié)序,所以通過(guò)inet_pton函數(shù)轉(zhuǎn)換后的ip地址輸出為:9080706

當(dāng)通過(guò)ntohl函數(shù)轉(zhuǎn)換回主機(jī)ip地址后輸出為:6070809

因?yàn)槲业谋镜刂鳈C(jī)使用的是小段字節(jié)序,所以轉(zhuǎn)換后的循序和ip地址順序一致,大端字節(jié)序則反過(guò)來(lái)了;

如果需要將網(wǎng)絡(luò)的ip地址轉(zhuǎn)換為字符串,則需要使用inet_ntop函數(shù);

如果需要將字符串ip地址轉(zhuǎn)換為網(wǎng)絡(luò)ip地址,則需要使用inet_pton函數(shù)。

在服務(wù)器中,如果有多個(gè)網(wǎng)絡(luò),一般我們都會(huì)綁定所有網(wǎng)卡,會(huì)進(jìn)行如下設(shè)置:

server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	// 監(jiān)聽(tīng)本地所有IP地址

INADDR_ANY是一個(gè)宏,即為0的宏,他轉(zhuǎn)換后賦值給結(jié)構(gòu)體實(shí)際上是:0.0.0.0這個(gè)ip地址。

三、Socket編程函數(shù)

1. socket函數(shù)

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int socket (int domain, int type, int protocol);

domain:

    AF_INET 這是大多數(shù)用來(lái)產(chǎn)生socket的協(xié)議,使用TCP或UDP來(lái)傳輸,用IPv4的地址。

    AF_INET6 與上面類(lèi)似,不過(guò)是來(lái)用IPv6的地址。

    AF_UNIX 本地協(xié)議,使用在Unix和Linux系統(tǒng)上,一般都是當(dāng)客戶(hù)端和服務(wù)器在同一臺(tái)及其上的時(shí)候使用。

type:

    SOCK_STREAM 這個(gè)協(xié)議是按照順序的、可靠的、數(shù)據(jù)完整的基于字節(jié)流的連接。這是一個(gè)使用最多的socket類(lèi)型,這個(gè)socket是使用TCP來(lái)進(jìn)行傳輸。

    SOCK_DGRAM 這個(gè)協(xié)議是無(wú)連接的、固定長(zhǎng)度的傳輸調(diào)用。該協(xié)議是不可靠的,使用UDP來(lái)進(jìn)行它的連接。

    SOCK_SEQPACKET 該協(xié)議是雙線路的、可靠的連接,發(fā)送固定長(zhǎng)度的數(shù)據(jù)包進(jìn)行傳輸。必須把這個(gè)包完整的接受才能進(jìn)行讀取。

    SOCK_RAW socket類(lèi)型提供單一的網(wǎng)絡(luò)訪問(wèn),這個(gè)socket類(lèi)型使用ICMP公共協(xié)議。(ping、traceroute使用該協(xié)議)。

    SOCK_RDM 這個(gè)類(lèi)型是很少使用的,在大部分的操作系統(tǒng)上沒(méi)有實(shí)現(xiàn),它是提供給數(shù)據(jù)鏈路層使用,不保證數(shù)據(jù)包的順序。

protocol:

    傳 0 表示使用默認(rèn)協(xié)議。

返回值:

    成功:返回指向新創(chuàng)建的socket的文件描述符,失敗:返回 -1,設(shè)置errno

可以使用以下方式進(jìn)行打印輸出失敗報(bào)錯(cuò)信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

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

對(duì)于IPv4,domain 參數(shù)指定為AF_INET。

對(duì)于TCP協(xié)議,type 參數(shù)指定為SOCK_STREAM,表示面向流的傳輸協(xié)議。如果是UDP協(xié)議,則type參數(shù)指定為SOCK_DGRAM,表示面向數(shù)據(jù)報(bào)的傳輸協(xié)議。

protocol 參數(shù)的介紹 - 略,指定為0即可。

例:

int sock;
sock = socket(AF_INET, SOCK_STREAM, 0);

2. bind 函數(shù)

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:

    socket文件描述符

addr:

    構(gòu)造出IP地址加端口號(hào)的結(jié)構(gòu)體

addrlen:

    sizeof(addr)長(zhǎng)度

返回值:

    成功 返回 0,失敗 返回 -1, 設(shè)置 errno

 可以使用以下方式進(jìn)行打印輸出失敗報(bào)錯(cuò)信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

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

bind()的作用是將參數(shù)sockfd和addr綁定在一起,使sockfd這個(gè)用于網(wǎng)絡(luò)通訊的文件描述符監(jiān)聽(tīng)addr所描述的地址和端口號(hào)。

例:

struct sockaddr_in servaddr;            // 定義結(jié)構(gòu)體
bzero(&servaddr, sizeof(servaddr));     // 將整個(gè)結(jié)構(gòu)體清零
 
// 設(shè)置地址類(lèi)型為AF_INET(IPv4)
servaddr.sin_family = AF_INET;          
 
/* 網(wǎng)絡(luò)地址為INADDR_ANY,這個(gè)宏表示本地的任意IP地址,因?yàn)榉?wù)器可能有多個(gè)網(wǎng)卡,
   每個(gè)網(wǎng)卡也可能綁定多個(gè)IP地址,這樣設(shè)置可以在所有的IP地址上監(jiān)聽(tīng),
   直到與某個(gè)客戶(hù)端建立了連接時(shí)才確定下來(lái)到底用哪個(gè)IP地址. */
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 
// 設(shè)置端口號(hào)為5000
servaddr.sin_port = htons(5000);
 
// 綁定
bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr));

3. listen 函數(shù)

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int listen (int sockfd, int backlog);

sockfd:

    socket 文件描述符

backlog:

    在Linux 系統(tǒng)中,它是指排隊(duì)等待建立3次握手隊(duì)列長(zhǎng)度。(客戶(hù)端同時(shí)進(jìn)行連接服務(wù)器的個(gè)數(shù))

返回值:

    成功 返回 0,失敗 返回 -1, 設(shè)置 errno

 可以使用以下方式進(jìn)行打印輸出失敗報(bào)錯(cuò)信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

查看一下系統(tǒng)默認(rèn)backlog

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

改變 系統(tǒng)限制的backlog 大小

1. 打開(kāi)文件

        vim /etc/sysctl.conf


2. 在文件最后添加
        net.core.somaxconn = 1024

        net.ipv4.tcp_max_syn_backlog = 1024

3. 保存,然后執(zhí)行
        sysctl -p

如下圖:(修改系統(tǒng)默認(rèn)backlog為1024)

 為什么要修改呢?如果不修改,即使我們?cè)诖a里設(shè)置10240(listen(sock, 10240);),它也還是按照系統(tǒng)默認(rèn)的值來(lái)設(shè)置的!

典型的服務(wù)器程序可以同時(shí)服務(wù)于多個(gè)客戶(hù)端,當(dāng)有客戶(hù)端發(fā)起連接時(shí),服務(wù)器調(diào)用的accept()返回并接受這個(gè)連接,如果有大量的客戶(hù)端發(fā)起連接而服務(wù)器來(lái)不及處理,尚未accept的客戶(hù)端就處于連接等待狀態(tài),listen()聲明sockfd處于監(jiān)聽(tīng)狀態(tài),并且最多允許有backlog個(gè)客戶(hù)端處于連接待狀態(tài),如果接收到更多的連接請(qǐng)求就忽略。

例:

// 監(jiān)聽(tīng),同時(shí)監(jiān)聽(tīng)128個(gè)請(qǐng)求
listen(sock, 128);

4. accept 函數(shù)

#include <sys/types.h>      /* See NOTES */

#include <sys/socket.h>

int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockdf:

    socket文件描述符

addr:

    傳出參數(shù),返回連接客戶(hù)端地址信息,含IP地址和端口號(hào)

addrlen:

    傳入傳出參數(shù)(值-結(jié)果),傳入sizeof(addr)大小,函數(shù)返回時(shí)返回真正接收到地址結(jié)構(gòu)體的大小

返回值:

    成功返回一個(gè)新的socket文件描述符,用于和客戶(hù)端通信,失敗返回 -1,設(shè)置errno

 可以使用以下方式進(jìn)行打印輸出失敗報(bào)錯(cuò)信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

三次握手完成后,服務(wù)器調(diào)用accept()接受連接,如果服務(wù)器調(diào)用accept()時(shí)還沒(méi)有客戶(hù)端的連接請(qǐng)求,就阻塞等待直到有客戶(hù)端連接上來(lái)。

addr 是一個(gè)傳出參數(shù),accept()返回時(shí)傳出客戶(hù)端的地址和端口號(hào);如果給addr參數(shù)傳NULL,表示不關(guān)心客戶(hù)端的地址。

addrlen 參數(shù)是一個(gè)傳入傳出參數(shù)(value-result argument),傳入的是調(diào)用者提供的緩沖區(qū)addr的長(zhǎng)度以避免緩沖區(qū)溢出問(wèn)題,傳出的是客戶(hù)端地址結(jié)構(gòu)體的實(shí)際長(zhǎng)度(有可能沒(méi)有占滿(mǎn)調(diào)用者提供的緩沖區(qū))。

例:

struct sockaddr_in client;
int client_sock;
		
socklen_t client_addr_len;		
client_addr_len = sizeof(client);
// 接受
client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);

5. connect 函數(shù)

客戶(hù)端使用!

#include <sys/types.h>                 /* See NOTES */

#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ù),傳入sizeof(addr)大小

返回值:

  成功 返回 0,失敗 返回 -1, 設(shè)置 errno

 可以使用以下方式進(jìn)行打印輸出失敗報(bào)錯(cuò)信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

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

例:

int sockfd = 0;
struct sockaddr_in serveraddr;
 
sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
serveraddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
serveraddr.sin_port = htons(5000);
 
// 連接服務(wù)器
connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

6. read 函數(shù)

#include <unistd.h>

ssize_t read (int fd, void *buf, size_t count);

fd:

    socket文件描述符;

buf:

    存儲(chǔ)讀取到的數(shù)據(jù),一般傳char *類(lèi)型或字符數(shù)組;

count:

    指定最多讀取的大小。

返回值:

    讀取成功返回讀取到的字節(jié)數(shù),讀取失敗返回 -1,設(shè)置errno

 可以使用以下方式進(jìn)行打印輸出失敗報(bào)錯(cuò)信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

從socket文件符中,讀取count指定的大小以?xún)?nèi)的數(shù)據(jù)存儲(chǔ)到buf中。

例:

int client_sock;
char buf[256];
// 對(duì)client_sock的賦值這里省略...
 
len = read(client_sock, buf, sizeof(buf)-1);

7. write 函數(shù)

#include <unistd.h>

ssize_t write (int fd, const void *buf, size_t count);

fd:

    socket文件描述符;

buf:

    需要發(fā)送(寫(xiě)入)的數(shù)據(jù);

count:

    指定最多發(fā)送(寫(xiě)入)的大小。

返回值:

    發(fā)送(寫(xiě)入)成功返回寫(xiě)入的字節(jié)數(shù),發(fā)送(寫(xiě)入)失敗返回 -1,設(shè)置errno

 可以使用以下方式進(jìn)行打印輸出失敗報(bào)錯(cuò)信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

例:

int client_sock;
char buf[256];
// 對(duì)client_sock的賦值這里省略...
 
len = write(client_sock, buf, sizeof(buf)-1);

8. close 函數(shù)

#include <unistd.h>

int close (int fd);

fd:

    socket文件描述符;

返回值:

    成功返回 0,失敗返回 -1,并適當(dāng)設(shè)置errno。

 可以使用以下方式進(jìn)行打印輸出失敗報(bào)錯(cuò)信息:

fprintf(stderr, " errno:%s\n", strerror(errno));

close()關(guān)閉一個(gè)文件描述符。

例:

int client_sock;
// client_sock= socket(AF_INET, SOCK_STREAM, 0);
 
close(client_sock);

四、回聲服務(wù)器案例

描述:

客戶(hù)端連接服務(wù)器,給服務(wù)器發(fā)送“hello world!”,服務(wù)器接收到后,將信息打印輸出后,原封不動(dòng)的給客戶(hù)端發(fā)送回去,客戶(hù)端接收到到后,也就數(shù)據(jù)打印輸出,程序結(jié)束。

1. 服務(wù)器

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>        // strerror
#include <ctype.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <errno.h>
 
#define SERVER_PORT 5000
 
 
int main(void) {
	
	int ret = 0;
	int sock;	// 通信套接字
	struct sockaddr_in server_addr;
	
	// 1.創(chuàng)建通信套接字
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sock) {
		fprintf(stderr, "create socket error, reason: %s\n", strerror(errno));
		exit(-1);
	}
	
	// 2.清空標(biāo)簽,寫(xiě)上地址和端口號(hào)
	bzero(&server_addr, sizeof(server_addr));
	
	server_addr.sin_family = AF_INET;	// 選擇協(xié)議組ipv4
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	// 監(jiān)聽(tīng)本地所有IP地址
	server_addr.sin_port = htons(SERVER_PORT);			// 綁定端口號(hào)
	
	// 3.綁定
	ret = bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if (-1 == ret) {
		fprintf(stderr, "socket bind error, reason: %s\n", strerror(errno));
		close(sock);
		exit(-2);
	}
	
	// 4.監(jiān)聽(tīng),同時(shí)監(jiān)聽(tīng)128個(gè)請(qǐng)求
	ret = listen(sock, 128);
	if (-1 == ret) {
		fprintf(stderr, "listen error, reason: %s\n", strerror(errno));
		close(sock);
		exit(-2);
	}
	
	printf("等待客戶(hù)端的鏈接\n");
	
	int done = 1;
	
	while (done) {
		
		struct sockaddr_in client;
		int client_sock;
		char client_ip[64];
		int len = 0;
		char buf[256];
		
		socklen_t client_addr_len;		
		client_addr_len = sizeof(client);
		// 5.接受
		client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);
		if (-1 == client_sock) {
			perror("accept error");
			close(sock);
			exit(-3);
		}
		
		// 打印客戶(hù)端IP地址和端口號(hào)
		printf("client ip: %s\t port: %d\n",
				inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)),
				ntohs(client.sin_port));
		
		
		// 6.讀取客戶(hù)端發(fā)送的數(shù)據(jù)
		len = read(client_sock, buf, sizeof(buf)-1);
		if (-1 == len) {
			perror("read error");
			close(sock);
			close(client_sock);
			exit(-4);
		}
		
		buf[len] = '\0';
		printf("recive[%d]: %s\n", len, buf);
		
		// 7.給客戶(hù)端發(fā)送數(shù)據(jù)
		len = write(client_sock, buf, len);
		if (-1 == len) {
			perror("write error");
			close(sock);
			close(client_sock);
			exit(-5);
		}
		
		printf("write finished. len: %d\n", len);
		// 8.關(guān)閉客戶(hù)端套接字
		close(client_sock);
	}
	
	// 9.關(guān)閉服務(wù)器套接字
	close(sock);
	
	return 0;
}

2. 客戶(hù)端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
 
 
 
#define SERVER_PORT		5000
#define SERVER_IP		"127.0.0.1"
 
 
int main(int argc, char *argv[]) {
	
	int ret = 0;
	int sockfd = 0;			// 通信套接字
	char *message = NULL;
	struct sockaddr_in serveraddr;
	int n = 0;
	char buff[64];
	
	if (2 != argc) {	
		fprintf(stderr, "Usage: ./echo_client message \n");
		exit(1);
	}
	
	// 獲取第二個(gè)參數(shù)的字符串
	message = argv[1];
	printf("message: %s\n", message);
	
		// 1.創(chuàng)建通信套接字
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd) {
		perror("create sockfd error");
		exit(-1);
	}
	
	// 2.清空標(biāo)簽,寫(xiě)上地址和端口號(hào)
	bzero(&serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;							// IPv4
	inet_pton(AF_INET, SERVER_IP, &serveraddr.sin_addr);		// 服務(wù)器地址
	serveraddr.sin_port = htons(SERVER_PORT);					// 服務(wù)器端口號(hào)
	
	// 3.連接服務(wù)器
	ret = connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
	if (-1 == ret) {
		perror("connect error");
		close(sockfd);
		exit(-2);
	}
	
	// 4.給服務(wù)器發(fā)送數(shù)據(jù)
	ret = write(sockfd, message, strlen(message));
	if (-1 == ret) {
		perror("write error");
		close(sockfd);
		exit(-3);
	}
	
	// 5.接受服務(wù)器發(fā)送過(guò)來(lái)的數(shù)據(jù)
	n = read(sockfd, buff, sizeof(buff)-1);
	if (-1 == n) {
		perror("read error");
		close(sockfd);
		exit(-4);
	}
	
	if (n > 0) {
		buff[n] = '\0';
		printf("receive: %s\n", buff);
	} else {
		perror("error!!!\n");
	}
	
	printf("client finished.\n");
	// 6.關(guān)閉套接字
	close(sockfd);
	
	return 0;
}

3. 運(yùn)行測(cè)試

1. 服務(wù)器

ygt@YGT:~/echo_server$ gcc echo_server.c -o echo_server
ygt@YGT:~/echo_server$ ./echo_server
等待客戶(hù)端的鏈接
client ip: 127.0.0.1     port: 41168
recive[12]: hello world!
write finished. len: 12

這里打印客戶(hù)端的IP地址是127.0.0.1,是因?yàn)槲沂窃谕慌_(tái)Linux系統(tǒng)中進(jìn)行測(cè)試的,所以打印的是這個(gè)本地地址。

2. 客戶(hù)端

root@YGT:/home/ygt/echo_server# gcc echo_client.c -o echo_client
root@YGT:/home/ygt/echo_server# ./echo_client "hello world!"
message: hello world!
receive: hello world!
client finished.

再來(lái)測(cè)試一下,在Linux中運(yùn)行服務(wù)器程序,然后再window環(huán)境使用cmd控制臺(tái)敲命令telnet去連接服務(wù)器。

才cmd中,telnet 后面接 服務(wù)器的ip地址 和 端口號(hào)

 當(dāng)按下回車(chē)鍵后,就連接上服務(wù)器了,服務(wù)器也接受到了客戶(hù)端的IP地址和端口號(hào),并將其打印出來(lái);然后客戶(hù)端將字符 ‘h’ 發(fā)送給了服務(wù)器,服務(wù)器接收到后將其打印出來(lái),然后給客戶(hù)端也發(fā)送字符 'h',但是我們?cè)赾md上是沒(méi)有接收功能的,所以就沒(méi)有接收到服務(wù)器發(fā)送過(guò)來(lái)的消息;最后服務(wù)器發(fā)送完成后就close斷開(kāi)了和客戶(hù)端的連接,cmd這邊就提示“遺失對(duì)主機(jī)的連接”。

五、總結(jié)

Linux環(huán)境中的C/C++ socket 與Window環(huán)境中的C/C++ socket類(lèi)似。

創(chuàng)建服務(wù)器時(shí)需要按照指定流程來(lái)創(chuàng)建,根據(jù)上面圖Socket 通信創(chuàng)建流程圖來(lái)創(chuàng)建即可。

注意調(diào)用系統(tǒng)函數(shù)失敗時(shí),可以打印失敗原因幫助我們定位問(wèn)題。

到此這篇關(guān)于C/C++ Linux Socket網(wǎng)絡(luò)編程的文章就介紹到這了,更多相關(guān)C/C++ Linux Socket網(wǎng)絡(luò)編程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C語(yǔ)言每日練習(xí)之二叉堆

    C語(yǔ)言每日練習(xí)之二叉堆

    這篇文章主要為大家介紹了C語(yǔ)言二叉堆,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-01-01
  • 2048小游戲C語(yǔ)言實(shí)現(xiàn)代碼

    2048小游戲C語(yǔ)言實(shí)現(xiàn)代碼

    這篇文章主要為大家詳細(xì)介紹了2048小游戲C語(yǔ)言實(shí)現(xiàn)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • C++菱形繼承和虛繼承的實(shí)現(xiàn)

    C++菱形繼承和虛繼承的實(shí)現(xiàn)

    本文主要介紹了C++菱形繼承和虛繼承的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • C語(yǔ)言中輸入函數(shù)(scanf()、fgets()和gets())的區(qū)別詳解

    C語(yǔ)言中輸入函數(shù)(scanf()、fgets()和gets())的區(qū)別詳解

    這篇文章主要給大家介紹了關(guān)于C語(yǔ)言中三種輸入函數(shù)(scanf()、fgets()和gets())區(qū)別的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-11-11
  • C語(yǔ)言switch 語(yǔ)句的用法詳解

    C語(yǔ)言switch 語(yǔ)句的用法詳解

    本文主要介紹C 語(yǔ)言中的switch 語(yǔ)句,這里提供示例代碼和詳細(xì)介紹,希望能幫助學(xué)習(xí)C語(yǔ)言的小伙伴
    2016-07-07
  • c++ STL容器總結(jié)之:vertor與list的應(yīng)用

    c++ STL容器總結(jié)之:vertor與list的應(yīng)用

    本篇文章對(duì)c++中STL容器中的vertor與list的應(yīng)用進(jìn)行了詳細(xì)的分析解釋。需要的朋友參考下
    2013-05-05
  • 如何在c語(yǔ)言下關(guān)閉socket

    如何在c語(yǔ)言下關(guān)閉socket

    如果不主動(dòng)關(guān)閉socket的話(huà),系統(tǒng)不會(huì)自動(dòng)關(guān)閉的,除非當(dāng)前進(jìn)程掛掉了,操作系統(tǒng)把占用的socket回收了才會(huì)關(guān)閉。下面小編來(lái)簡(jiǎn)單介紹下
    2019-05-05
  • C語(yǔ)言進(jìn)階棧幀示例詳解教程

    C語(yǔ)言進(jìn)階棧幀示例詳解教程

    這篇文章主要為大家介紹了C語(yǔ)言進(jìn)階棧幀的示例詳解教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-02-02
  • OpenCV圖像算法實(shí)現(xiàn)圖像切分圖像合并示例

    OpenCV圖像算法實(shí)現(xiàn)圖像切分圖像合并示例

    這篇文章主要為大家介紹了python圖像算法OpenCV實(shí)現(xiàn)圖像切分圖像合并操作示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • C語(yǔ)言實(shí)現(xiàn)三子棋游戲附注釋

    C語(yǔ)言實(shí)現(xiàn)三子棋游戲附注釋

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)三子棋游戲附注釋?zhuān)闹惺纠a介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-06-06

最新評(píng)論