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

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

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

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

一、Socket簡介

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

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

如下圖

 Socket通信3要素:

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

Socket通信模型

 Socket被稱之為套接字。

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

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ū)。

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

二、Socket編程基礎

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

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

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

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

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

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

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

所以,我們在代碼中必須要將ip地址和端口號做相應的轉換,轉換為網(wǎng)絡字節(jié)序才可以進行通訊。

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

為什么需要轉換呢?

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

需要通過以下接口進行轉換:

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

l 結尾的函數(shù)用于ip地址轉換,s 結尾的函數(shù)用于端口號的轉換。

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

2. sockaddr數(shù)據(jù)結構

我們在使用socket中,需要使用結構體sockaddr_in將IP地址和端口號等保存,然后用于綁定socket;

但進行綁定時,我們卻要將結構體sockaddr_in強制類型轉換為結構體sockaddr,這是為什么呢?

由于歷史原因,一開始是沒有結構體sockaddr_in,只有結構體sockaddr。

后來為了適配ipv4的到來,將結構體sockaddr細化為結構體sockaddr_in,如上圖。

兩個結構體如下:

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結構體表示,包括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 */

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

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

但是實際項目中我們設置ip地址大多數(shù)都是字符串,所以得使用特定的函數(shù)去進行轉換。

#include <arpa/inet.h>

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

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

af 取值可選為 AF_INET 和 AF_INET6 ,即和 ipv4 和ipv6對應支持IPv4和IPv6;

src 是轉換前ip,dst 是轉換后ip;

其中inet_pton和inet_ntop不僅可以轉換IPv4的in_addr,還可以轉換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;
}

 運行結果:

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

因為網(wǎng)絡上使用的是大端字節(jié)序,所以通過inet_pton函數(shù)轉換后的ip地址輸出為:9080706

當通過ntohl函數(shù)轉換回主機ip地址后輸出為:6070809

因為我的本地主機使用的是小段字節(jié)序,所以轉換后的循序和ip地址順序一致,大端字節(jié)序則反過來了;

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

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

在服務器中,如果有多個網(wǎng)絡,一般我們都會綁定所有網(wǎng)卡,會進行如下設置:

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

INADDR_ANY是一個宏,即為0的宏,他轉換后賦值給結構體實際上是:0.0.0.0這個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ù)用來產(chǎn)生socket的協(xié)議,使用TCP或UDP來傳輸,用IPv4的地址。

    AF_INET6 與上面類似,不過是來用IPv6的地址。

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

type:

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

    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é)議。

返回值:

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

可以使用以下方式進行打印輸出失敗報錯信息:

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

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

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

對于TCP協(xié)議,type 參數(shù)指定為SOCK_STREAM,表示面向流的傳輸協(xié)議。如果是UDP協(xié)議,則type參數(shù)指定為SOCK_DGRAM,表示面向數(shù)據(jù)報的傳輸協(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:

    構造出IP地址加端口號的結構體

addrlen:

    sizeof(addr)長度

返回值:

    成功 返回 0,失敗 返回 -1, 設置 errno

 可以使用以下方式進行打印輸出失敗報錯信息:

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

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

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

例:

struct sockaddr_in servaddr;            // 定義結構體
bzero(&servaddr, sizeof(servaddr));     // 將整個結構體清零
 
// 設置地址類型為AF_INET(IPv4)
servaddr.sin_family = AF_INET;          
 
/* 網(wǎng)絡地址為INADDR_ANY,這個宏表示本地的任意IP地址,因為服務器可能有多個網(wǎng)卡,
   每個網(wǎng)卡也可能綁定多個IP地址,這樣設置可以在所有的IP地址上監(jiān)聽,
   直到與某個客戶端建立了連接時才確定下來到底用哪個IP地址. */
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 
// 設置端口號為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)中,它是指排隊等待建立3次握手隊列長度。(客戶端同時進行連接服務器的個數(shù))

返回值:

    成功 返回 0,失敗 返回 -1, 設置 errno

 可以使用以下方式進行打印輸出失敗報錯信息:

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

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

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

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

1. 打開文件

        vim /etc/sysctl.conf


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

        net.ipv4.tcp_max_syn_backlog = 1024

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

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

 為什么要修改呢?如果不修改,即使我們在代碼里設置10240(listen(sock, 10240);),它也還是按照系統(tǒng)默認的值來設置的!

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

例:

// 監(jiān)聽,同時監(jiān)聽128個請求
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ù),返回連接客戶端地址信息,含IP地址和端口號

addrlen:

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

返回值:

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

 可以使用以下方式進行打印輸出失敗報錯信息:

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

三次握手完成后,服務器調用accept()接受連接,如果服務器調用accept()時還沒有客戶端的連接請求,就阻塞等待直到有客戶端連接上來。

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

addrlen 參數(shù)是一個傳入傳出參數(shù)(value-result argument),傳入的是調用者提供的緩沖區(qū)addr的長度以避免緩沖區(qū)溢出問題,傳出的是客戶端地址結構體的實際長度(有可能沒有占滿調用者提供的緩沖區(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ù)

客戶端使用!

#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ù),指定服務器端地址信息,含IP地址和端口號

addrlen:

    傳入?yún)?shù),傳入sizeof(addr)大小

返回值:

  成功 返回 0,失敗 返回 -1, 設置 errno

 可以使用以下方式進行打印輸出失敗報錯信息:

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

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

例:

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);
 
// 連接服務器
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:

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

count:

    指定最多讀取的大小。

返回值:

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

 可以使用以下方式進行打印輸出失敗報錯信息:

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

從socket文件符中,讀取count指定的大小以內的數(shù)據(jù)存儲到buf中。

例:

int client_sock;
char buf[256];
// 對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ā)送(寫入)的數(shù)據(jù);

count:

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

返回值:

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

 可以使用以下方式進行打印輸出失敗報錯信息:

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

例:

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

8. close 函數(shù)

#include <unistd.h>

int close (int fd);

fd:

    socket文件描述符;

返回值:

    成功返回 0,失敗返回 -1,并適當設置errno。

 可以使用以下方式進行打印輸出失敗報錯信息:

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

close()關閉一個文件描述符。

例:

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

四、回聲服務器案例

描述:

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

1. 服務器

#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.清空標簽,寫上地址和端口號
	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)聽本地所有IP地址
	server_addr.sin_port = htons(SERVER_PORT);			// 綁定端口號
	
	// 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)聽,同時監(jiān)聽128個請求
	ret = listen(sock, 128);
	if (-1 == ret) {
		fprintf(stderr, "listen error, reason: %s\n", strerror(errno));
		close(sock);
		exit(-2);
	}
	
	printf("等待客戶端的鏈接\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);
		}
		
		// 打印客戶端IP地址和端口號
		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.讀取客戶端發(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.給客戶端發(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.關閉客戶端套接字
		close(client_sock);
	}
	
	// 9.關閉服務器套接字
	close(sock);
	
	return 0;
}

2. 客戶端

#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);
	}
	
	// 獲取第二個參數(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.清空標簽,寫上地址和端口號
	bzero(&serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;							// IPv4
	inet_pton(AF_INET, SERVER_IP, &serveraddr.sin_addr);		// 服務器地址
	serveraddr.sin_port = htons(SERVER_PORT);					// 服務器端口號
	
	// 3.連接服務器
	ret = connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
	if (-1 == ret) {
		perror("connect error");
		close(sockfd);
		exit(-2);
	}
	
	// 4.給服務器發(fā)送數(shù)據(jù)
	ret = write(sockfd, message, strlen(message));
	if (-1 == ret) {
		perror("write error");
		close(sockfd);
		exit(-3);
	}
	
	// 5.接受服務器發(fā)送過來的數(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.關閉套接字
	close(sockfd);
	
	return 0;
}

3. 運行測試

1. 服務器

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

這里打印客戶端的IP地址是127.0.0.1,是因為我是在同一臺Linux系統(tǒng)中進行測試的,所以打印的是這個本地地址。

2. 客戶端

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.

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

才cmd中,telnet 后面接 服務器的ip地址 和 端口號

 當按下回車鍵后,就連接上服務器了,服務器也接受到了客戶端的IP地址和端口號,并將其打印出來;然后客戶端將字符 ‘h’ 發(fā)送給了服務器,服務器接收到后將其打印出來,然后給客戶端也發(fā)送字符 'h',但是我們在cmd上是沒有接收功能的,所以就沒有接收到服務器發(fā)送過來的消息;最后服務器發(fā)送完成后就close斷開了和客戶端的連接,cmd這邊就提示“遺失對主機的連接”。

五、總結

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

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

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

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

相關文章

  • C語言每日練習之二叉堆

    C語言每日練習之二叉堆

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

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

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

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

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

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

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

    C語言switch 語句的用法詳解

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

    c++ STL容器總結之:vertor與list的應用

    本篇文章對c++中STL容器中的vertor與list的應用進行了詳細的分析解釋。需要的朋友參考下
    2013-05-05
  • 如何在c語言下關閉socket

    如何在c語言下關閉socket

    如果不主動關閉socket的話,系統(tǒng)不會自動關閉的,除非當前進程掛掉了,操作系統(tǒng)把占用的socket回收了才會關閉。下面小編來簡單介紹下
    2019-05-05
  • C語言進階棧幀示例詳解教程

    C語言進階棧幀示例詳解教程

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

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

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

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

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)三子棋游戲附注釋,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-06-06

最新評論