C/C++?Linux?Socket網(wǎng)絡(luò)編程流程分析
之前已經(jīng)學習了QT的socket編程 和 C/C++在window環(huán)境的socket編程,現(xiàn)在再來學習一波C/C++在Linux環(huán)境下的socket編程,為以后學習C++ Linux 服務(wù)器開發(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ū)別是文件主要應(yīng)用于本地持久化數(shù)據(jù)的讀寫,而套接字多應(yīng)用于網(wǎng)絡(luò)進程間數(shù)據(jù)的傳遞。
在TCP/IP協(xié)議中,“IP地址+TCP或UDP端口號”唯一標識網(wǎng)絡(luò)通訊中的一個進程。“IP地址+端口號”就對應(yīng)一個socket。欲建立連接的兩個進程各自有一個socket來標識,那么這兩個socket組成的socket pair就唯一標識一個連接。因此可以用Socket來描述網(wǎng)絡(luò)連接的一對一關(guān)系。
套接字通信原理如下圖所示:

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

二、Socket編程基礎(chǔ)
1. 網(wǎng)絡(luò)字節(jié)序
在計算機世界里,有兩種字節(jié)序:
大端字節(jié)序 --- 低地址高字節(jié),高地址低字節(jié)
小端字節(jié)序 --- 低地址低字節(jié),高地址高字節(jié)

內(nèi)存中的多字節(jié)數(shù)據(jù)相對于內(nèi)存地址有大端和小端之分,磁盤文件中的多字節(jié)數(shù)據(jù)相對于文件中的偏移地址也有大端小端之分,網(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é)。
所以,我們在代碼中必須要將ip地址和端口號做相應(yīng)的轉(zhuǎn)換,轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序才可以進行通訊。
大多數(shù)使用 htonl 和 htons 。
為什么需要轉(zhuǎn)換呢?
假設(shè)本地主機使用的是小端字節(jié)序,而對方主機使用的是大端字節(jié)序;你發(fā)送數(shù)據(jù)過去的地址順序是:0x06b3,而對方接受到的卻是:0xb306;這樣數(shù)據(jù)就亂了,所以需要進行轉(zhuǎ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位長整數(shù),s表示16位短整數(shù)。
l 結(jié)尾的函數(shù)用于ip地址轉(zhuǎn)換,s 結(jié)尾的函數(shù)用于端口號的轉(zhuǎn)換。
如果主機是小端字節(jié)序,這些函數(shù)將參數(shù)做相應(yīng)的大小端轉(zhuǎn)換然后返回,如果主機是大端字節(jié)序,這些函數(shù)不做轉(zhuǎn)換,將參數(shù)原封不動地返回。
2. sockaddr數(shù)據(jù)結(jié)構(gòu)
我們在使用socket中,需要使用結(jié)構(gòu)體sockaddr_in將IP地址和端口號等保存,然后用于綁定socket;
但進行綁定時,我們卻要將結(jié)構(gòu)體sockaddr_in強制類型轉(zhuǎn)換為結(jié)構(gòu)體sockaddr,這是為什么呢?

由于歷史原因,一開始是沒有結(jié)構(gòu)體sockaddr_in,只有結(jié)構(gòu)體sockaddr。
后來為了適配ipv4的到來,將結(jié)構(gòu)體sockaddr細化為結(jié)構(gòu)體sockaddr_in,如上圖。
兩個結(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位端口號和32位IP地址,但是sock API的實現(xiàn)早于ANSI C標準化,那時還沒有void *類型,因此這些像bind 、accept函數(shù)的參數(shù)都用struct sockaddr *類型表示,在傳遞參數(shù)之前要強制類型轉(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 兩個函數(shù)進行ip地址的轉(zhuǎn)換,但只能將uint32_t類型的地址進行轉(zhuǎn)換,例如:INADDR_ANY ==> 0.0.0.0
但是實際項目中我們設(shè)置ip地址大多數(shù)都是字符串,所以得使用特定的函數(shù)去進行轉(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對應(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;
}運行結(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
因為網(wǎng)絡(luò)上使用的是大端字節(jié)序,所以通過inet_pton函數(shù)轉(zhuǎn)換后的ip地址輸出為:9080706
當通過ntohl函數(shù)轉(zhuǎn)換回主機ip地址后輸出為:6070809
因為我的本地主機使用的是小段字節(jié)序,所以轉(zhuǎn)換后的循序和ip地址順序一致,大端字節(jié)序則反過來了;
如果需要將網(wǎng)絡(luò)的ip地址轉(zhuǎn)換為字符串,則需要使用inet_ntop函數(shù);
如果需要將字符串ip地址轉(zhuǎn)換為網(wǎng)絡(luò)ip地址,則需要使用inet_pton函數(shù)。
在服務(wù)器中,如果有多個網(wǎng)絡(luò),一般我們都會綁定所有網(wǎng)卡,會進行如下設(shè)置:
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監(jiān)聽本地所有IP地址
INADDR_ANY是一個宏,即為0的宏,他轉(zhuǎn)換后賦值給結(jié)構(gòu)體實際上是: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)上,一般都是當客戶端和服務(wù)器在同一臺及其上的時候使用。
type:
SOCK_STREAM 這個協(xié)議是按照順序的、可靠的、數(shù)據(jù)完整的基于字節(jié)流的連接。這是一個使用最多的socket類型,這個socket是使用TCP來進行傳輸。
SOCK_DGRAM 這個協(xié)議是無連接的、固定長度的傳輸調(diào)用。該協(xié)議是不可靠的,使用UDP來進行它的連接。
SOCK_SEQPACKET 該協(xié)議是雙線路的、可靠的連接,發(fā)送固定長度的數(shù)據(jù)包進行傳輸。必須把這個包完整的接受才能進行讀取。
SOCK_RAW socket類型提供單一的網(wǎng)絡(luò)訪問,這個socket類型使用ICMP公共協(xié)議。(ping、traceroute使用該協(xié)議)。
SOCK_RDM 這個類型是很少使用的,在大部分的操作系統(tǒng)上沒有實現(xiàn),它是提供給數(shù)據(jù)鏈路層使用,不保證數(shù)據(jù)包的順序。
protocol:
傳 0 表示使用默認協(xié)議。
返回值:
成功:返回指向新創(chuàng)建的socket的文件描述符,失?。悍祷?-1,設(shè)置errno
可以使用以下方式進行打印輸出失敗報錯信息:
fprintf(stderr, " errno:%s\n", strerror(errno));
socket()打開一個網(wǎng)絡(luò)通訊端口,如果成功的話,就像open()一樣返回一個文件描述符,應(yīng)用程序可以像讀寫文件一樣用read/write在網(wǎng)絡(luò)上收發(fā)數(shù)據(jù),如果socket()調(diào)用出錯則返回-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:
構(gòu)造出IP地址加端口號的結(jié)構(gòu)體
addrlen:
sizeof(addr)長度
返回值:
成功 返回 0,失敗 返回 -1, 設(shè)置 errno
可以使用以下方式進行打印輸出失敗報錯信息:
fprintf(stderr, " errno:%s\n", strerror(errno));
服務(wù)器程序所監(jiān)聽的網(wǎng)絡(luò)地址和端口號通常是固定不變的,客戶端程序得知服務(wù)器程序的地址和端口號后就可以向服務(wù)器發(fā)起連接,因此服務(wù)器需要調(diào)用bind綁定一個固定的網(wǎng)絡(luò)地址和端口號。
bind()的作用是將參數(shù)sockfd和addr綁定在一起,使sockfd這個用于網(wǎng)絡(luò)通訊的文件描述符監(jiān)聽addr所描述的地址和端口號。
例:
struct sockaddr_in servaddr; // 定義結(jié)構(gòu)體 bzero(&servaddr, sizeof(servaddr)); // 將整個結(jié)構(gòu)體清零 // 設(shè)置地址類型為AF_INET(IPv4) servaddr.sin_family = AF_INET; /* 網(wǎng)絡(luò)地址為INADDR_ANY,這個宏表示本地的任意IP地址,因為服務(wù)器可能有多個網(wǎng)卡, 每個網(wǎng)卡也可能綁定多個IP地址,這樣設(shè)置可以在所有的IP地址上監(jiān)聽, 直到與某個客戶端建立了連接時才確定下來到底用哪個IP地址. */ servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 設(shè)置端口號為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次握手隊列長度。(客戶端同時進行連接服務(wù)器的個數(shù))
返回值:
成功 返回 0,失敗 返回 -1, 設(shè)置 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)


為什么要修改呢?如果不修改,即使我們在代碼里設(shè)置10240(listen(sock, 10240);),它也還是按照系統(tǒng)默認的值來設(shè)置的!
典型的服務(wù)器程序可以同時服務(wù)于多個客戶端,當有客戶端發(fā)起連接時,服務(wù)器調(diào)用的accept()返回并接受這個連接,如果有大量的客戶端發(fā)起連接而服務(wù)器來不及處理,尚未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ù)(值-結(jié)果),傳入sizeof(addr)大小,函數(shù)返回時返回真正接收到地址結(jié)構(gòu)體的大小
返回值:
成功返回一個新的socket文件描述符,用于和客戶端通信,失敗返回 -1,設(shè)置errno
可以使用以下方式進行打印輸出失敗報錯信息:
fprintf(stderr, " errno:%s\n", strerror(errno));
三次握手完成后,服務(wù)器調(diào)用accept()接受連接,如果服務(wù)器調(diào)用accept()時還沒有客戶端的連接請求,就阻塞等待直到有客戶端連接上來。
addr 是一個傳出參數(shù),accept()返回時傳出客戶端的地址和端口號;如果給addr參數(shù)傳NULL,表示不關(guān)心客戶端的地址。
addrlen 參數(shù)是一個傳入傳出參數(shù)(value-result argument),傳入的是調(diào)用者提供的緩沖區(qū)addr的長度以避免緩沖區(qū)溢出問題,傳出的是客戶端地址結(jié)構(gòu)體的實際長度(有可能沒有占滿調(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ù)
客戶端使用!
#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地址和端口號
addrlen:
傳入?yún)?shù),傳入sizeof(addr)大小
返回值:
成功 返回 0,失敗 返回 -1, 設(shè)置 errno
可以使用以下方式進行打印輸出失敗報錯信息:
fprintf(stderr, " errno:%s\n", strerror(errno));
客戶端需要調(diào)用connect()連接服務(wù)器,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); // 連接服務(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:
存儲讀取到的數(shù)據(jù),一般傳char *類型或字符數(shù)組;
count:
指定最多讀取的大小。
返回值:
讀取成功返回讀取到的字節(jié)數(shù),讀取失敗返回 -1,設(shè)置errno
可以使用以下方式進行打印輸出失敗報錯信息:
fprintf(stderr, " errno:%s\n", strerror(errno));
從socket文件符中,讀取count指定的大小以內(nèi)的數(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,設(shè)置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,并適當設(shè)置errno。
可以使用以下方式進行打印輸出失敗報錯信息:
fprintf(stderr, " errno:%s\n", strerror(errno));
close()關(guān)閉一個文件描述符。
例:
int client_sock; // client_sock= socket(AF_INET, SOCK_STREAM, 0); close(client_sock);
四、回聲服務(wù)器案例
描述:
客戶端連接服務(wù)器,給服務(wù)器發(fā)送“hello world!”,服務(wù)器接收到后,將信息打印輸出后,原封不動的給客戶端發(fā)送回去,客戶端接收到到后,也就數(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.清空標簽,寫上地址和端口號
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.關(guān)閉客戶端套接字
close(client_sock);
}
// 9.關(guān)閉服務(wù)器套接字
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); // 服務(wù)器地址
serveraddr.sin_port = htons(SERVER_PORT); // 服務(wù)器端口號
// 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ā)送過來的數(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. 運行測試
1. 服務(wù)器
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中運行服務(wù)器程序,然后再window環(huán)境使用cmd控制臺敲命令telnet去連接服務(wù)器。
才cmd中,telnet 后面接 服務(wù)器的ip地址 和 端口號

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

五、總結(jié)
Linux環(huán)境中的C/C++ socket 與Window環(huán)境中的C/C++ socket類似。
創(chuàng)建服務(wù)器時需要按照指定流程來創(chuàng)建,根據(jù)上面圖Socket 通信創(chuàng)建流程圖來創(chuàng)建即可。
注意調(diào)用系統(tǒng)函數(shù)失敗時,可以打印失敗原因幫助我們定位問題。
到此這篇關(guān)于C/C++ Linux Socket網(wǎng)絡(luò)編程的文章就介紹到這了,更多相關(guān)C/C++ Linux Socket網(wǎng)絡(luò)編程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 詳解從Linux源碼看Socket(TCP)的bind
- 詳解Linux使用ss命令結(jié)合zabbix對socket做監(jiān)控
- 從Linux源碼看Socket(TCP)Client端的Connect的示例詳解
- 局域網(wǎng)內(nèi)python socket實現(xiàn)windows與linux間的消息傳送
- Linux進程間通信方式之socket使用實例
- linux下socket編程常用頭文件(推薦)
- linux socket通訊獲取本地的源端口號的實現(xiàn)方法
- Linux UDP socket 設(shè)置為的非阻塞模式與阻塞模式區(qū)別
- Linux?socket函數(shù)詳解
相關(guān)文章
C語言中輸入函數(shù)(scanf()、fgets()和gets())的區(qū)別詳解
這篇文章主要給大家介紹了關(guān)于C語言中三種輸入函數(shù)(scanf()、fgets()和gets())區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧。2017-11-11
c++ STL容器總結(jié)之:vertor與list的應(yīng)用
本篇文章對c++中STL容器中的vertor與list的應(yīng)用進行了詳細的分析解釋。需要的朋友參考下2013-05-05

