C/C++?Linux?Socket網(wǎng)絡編程流程分析
之前已經(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語言中輸入函數(shù)(scanf()、fgets()和gets())的區(qū)別詳解
這篇文章主要給大家介紹了關于C語言中三種輸入函數(shù)(scanf()、fgets()和gets())區(qū)別的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧。2017-11-11