橙色
1、socket套接字介紹
所謂套接字,就是對網(wǎng)絡(luò)中不同主機(jī)上的應(yīng)用進(jìn)程之間進(jìn)行雙向通信的端點(diǎn)的抽象。
一個套接字就是網(wǎng)絡(luò)上進(jìn)程通信的一端,提供了應(yīng)用層進(jìn)程利用網(wǎng)絡(luò)協(xié)議交換數(shù)據(jù)的機(jī)制。從所處的地位來講,套接字上聯(lián)應(yīng)用進(jìn)程,下聯(lián)網(wǎng)絡(luò)協(xié)議棧,是應(yīng)用程序通過網(wǎng)絡(luò)協(xié)議進(jìn)程通信的接口,是應(yīng)用程序與網(wǎng)絡(luò)協(xié)議進(jìn)行交互的接口。
它是網(wǎng)絡(luò)環(huán)境中進(jìn)行通信的API,使用中每一個套接字都有一個與之相連進(jìn)程。通信時其中一個網(wǎng)絡(luò)應(yīng)用程序?qū)⒁獋鬏數(shù)囊欢涡畔懭胨诘闹鳈C(jī)socket中,該socket通過與網(wǎng)絡(luò)接口卡(NIC)相連的傳輸介質(zhì)將這段信息送到另一臺主機(jī)的socket中,使對方能夠接收到這段信息。socket是由IP地址和端口結(jié)合的
,提供應(yīng)用層進(jìn)程傳送數(shù)據(jù)包的機(jī)制。
socket本意上“插座”的意思,在Linux環(huán)境中,用于表示進(jìn)程間網(wǎng)絡(luò)通信的特殊文件類型。本質(zhì)上為內(nèi)核借助緩沖區(qū)形成的偽文件。把它設(shè)置為文件,方便我們進(jìn)行操作,我們可以通過文件描述符進(jìn)行操作。與管道類型,Linux系統(tǒng)將期封裝成文件的目的是為了統(tǒng)一接口,使得讀寫套接字和讀寫文件操作一樣。區(qū)別是管道應(yīng)用于本地進(jìn)程間通信,而套接字多用于網(wǎng)絡(luò)進(jìn)程間數(shù)據(jù)的傳遞。
socket是全雙工通信,即在同一時刻既可以數(shù)據(jù)讀入,也可以數(shù)據(jù)輸出。
IP地址(邏輯地址): 在網(wǎng)絡(luò)中唯一標(biāo)識一臺主機(jī)
端口號:在一臺主機(jī)中唯一標(biāo)識一個進(jìn)程
IP+端口號:在網(wǎng)絡(luò)環(huán)境中唯一標(biāo)識一個進(jìn)程
-服務(wù)器端:被動接受連接,一般不會主動發(fā)起連接
-客戶端:主動向服務(wù)器發(fā)起連接
2、字節(jié)序
簡介
現(xiàn)在CPU的累加器一次都能裝載(至少)4個字節(jié)(32位機(jī)),即一個整數(shù)。那么這4個字節(jié)在內(nèi)存中排列的順序?qū)⒂绊懰焕奂悠餮b載的整數(shù)值,這就是字節(jié)序問題。在各種計算機(jī)體系結(jié)構(gòu)中,對于字節(jié)、字等的存儲機(jī)制有所不同,因而引發(fā)了計算機(jī)通信領(lǐng)域中一個很重要的問題,即通信雙方交流的信息單元應(yīng)該以什么樣的順序進(jìn)行傳送。如果不達(dá)成一致的規(guī)則,通信雙方將無法進(jìn)行正確的編碼/譯碼從而導(dǎo)致通信失敗。
字節(jié)序,顧名思義寧節(jié)的順序,就是大于一個寧節(jié)類型的數(shù)據(jù)在內(nèi)存中的存放順序(一個字節(jié)的數(shù)據(jù)當(dāng)然就無需談順序的問題了)。
字節(jié)序分為大端字節(jié)序(Big-Endian)和小端字節(jié)序(Little-Endian)。大端字節(jié)序是指一個整數(shù)的高位字節(jié)存儲在內(nèi)存的低地址位置,低位字節(jié)存儲在內(nèi)存的高地址位置。小端字節(jié)序則是指一個整數(shù)的高位字節(jié)存儲在內(nèi)存高地址處,而低位字節(jié)則存儲在內(nèi)存的低地址處
。
顯然,一個數(shù),越靠左邊的是高位,越靠右邊的是低位
下面,編寫一個程序檢測當(dāng)前主機(jī)的字節(jié)序:
對聯(lián)合體不了解的可以參考這篇文章——C語言 | 聯(lián)合體詳解
/*
字節(jié)序:字節(jié)在內(nèi)存中存儲的順序。
小端字節(jié)序:數(shù)據(jù)的高位字節(jié)存儲在內(nèi)存的高位地址,低位字節(jié)存儲在內(nèi)存的低位地址
大端字節(jié)序:數(shù)據(jù)的低位字節(jié)存儲在內(nèi)存的高位地址,高位字節(jié)存儲在內(nèi)存的低位地址
*/
// 通過代碼檢測當(dāng)前主機(jī)的字節(jié)序
#include <stdio.h>
int main() {
union {
short value; // 2字節(jié)
char bytes[sizeof(short)]; // char[2]
} test;
test.value = 0x0102;
if((test.bytes[0] == 1) && (test.bytes[1] == 2)) {
printf("大端字節(jié)序\n");
} else if((test.bytes[0] == 2) && (test.bytes[1] == 1)) {
printf("小端字節(jié)序\n");
} else {
printf("未知\n");
}
return 0;
}
字節(jié)序轉(zhuǎn)換函數(shù)
當(dāng)格式化的數(shù)據(jù)在兩臺使用不同字節(jié)序的主機(jī)之間直接傳遞時,接收端必然會錯誤的解釋。解決問題的方法是:發(fā)送端總是把要發(fā)送的數(shù)據(jù)轉(zhuǎn)換成大端字節(jié)序數(shù)據(jù)后再發(fā)送,而接收端知道對方傳送過來的數(shù)據(jù)總是采用大端字節(jié)序,所以接收端可以根據(jù)自身采用的字節(jié)序決定是否對接收到的數(shù)據(jù)進(jìn)行轉(zhuǎn)換(小端機(jī)轉(zhuǎn)換,大端機(jī)不轉(zhuǎn)換)。
網(wǎng)絡(luò)字節(jié)順序
是TCPIP中規(guī)定好的一種數(shù)據(jù)表示格式,它與具體的CPU類型、操作系統(tǒng)等無關(guān),從而可以保證數(shù)據(jù)在不同主機(jī)之間傳輸時能夠被正確解釋,網(wǎng)絡(luò)字節(jié)順序采用大端排序方式。
BSD Socke t提供了封裝好的轉(zhuǎn)換接口,方便程序員使用。包括從主機(jī)字節(jié)序到網(wǎng)絡(luò)字節(jié)序的轉(zhuǎn)換函數(shù): htons,htonl;從網(wǎng)絡(luò)字節(jié)序到主機(jī)字節(jié)序的轉(zhuǎn)換函數(shù): ntohs、ntohl。
/*
h - host 主機(jī),主機(jī)字節(jié)序
to 轉(zhuǎn)換成什么
n - network 網(wǎng)絡(luò)字節(jié)序
s - short unsigned short 端口
l - long unsigned int IP
網(wǎng)絡(luò)通信時,需要將主機(jī)字節(jié)序轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序(大端),
另外一段獲取到數(shù)據(jù)以后根據(jù)情況將網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換成主機(jī)字節(jié)序。
// 轉(zhuǎn)換端口
uint16_t htons(uint16_t hostshort); // 主機(jī)字節(jié)序 - 網(wǎng)絡(luò)字節(jié)序
uint16_t ntohs(uint16_t netshort); // 網(wǎng)絡(luò)字節(jié)序 - 主機(jī)字節(jié)序
// 轉(zhuǎn)IP
uint32_t htonl(uint32_t hostlong); // 主機(jī)字節(jié)序 - 網(wǎng)絡(luò)字節(jié)序
uint32_t ntohl(uint32_t netlong); // 網(wǎng)絡(luò)字節(jié)序 - 主機(jī)字節(jié)序
*/
#include <stdio.h>
#include <arpa/inet.h>
int main() {
// htons 轉(zhuǎn)換端口
unsigned short a = 0x0102;
printf("a : %x\n", a);
unsigned short b = htons(a);
printf("b : %x\n", b);
printf("=======================\n");
// htonl 轉(zhuǎn)換IP
char buf[4] = {192, 168, 1, 100};
int num = *(int *)buf;
printf("num : %d\n", num);
int sum = htonl(num);
unsigned char *p = (char *)∑
printf("%d %d %d %d\n", *p, *(p+1), *(p+2), *(p+3));
printf("=======================\n");
// ntohl
unsigned char buf1[4] = {1, 1, 168, 192};
int num1 = *(int *)buf1;
int sum1 = ntohl(num1);
unsigned char *p1 = (unsigned char *)&sum1;
printf("%d %d %d %d\n", *p1, *(p1+1), *(p1+2), *(p1+3));
// ntohs
return 0;
}
問題:打印num是1677830336是怎么回事呢?
答: // 192 ???? 168 ???????1 ????????100
? // 11000000 10101000 000000001 01101000
//本機(jī)是小端字節(jié)序,所以在192在低位,100在高位,所以num為
// 01101000 ?00000001 ?10101000 ?11000000 = 1677830336
3、socket地址
socket網(wǎng)絡(luò)編程接口中表示socket地址是結(jié)構(gòu)體sockaddr,其定義如下:
#include <bits/socket.h>
struct sockaddr{ //已經(jīng)被廢棄掉
sa_family_t sa_family;
char sa_data[14];
};
typedef unsigned short int sa_family_t;
成員:
????sa_family成員是地址族類型(sa_family_t)的變量。地址族類型通常與協(xié)議類型對應(yīng)。常見的協(xié)議族和對應(yīng)的地址族如下所示:
協(xié)議族 | 地址族 | 描述 |
---|---|---|
PF_UNIX | AF_UNIX | UNIX本地域協(xié)議族 |
PF_INET | AF_INET | TCP/IPv4協(xié)議族 |
PF_INET6 | AF_INET6 | TCP/IPv6協(xié)議族 |
協(xié)議族 PF_*和地址族AF_*都定義在頭文件bits/socket.h中,二者值相同,可以混合使用(反正都是宏定義,宏定義是預(yù)處理階段進(jìn)行宏替換,所以混著用對編譯運(yùn)行不會有影響)
而sa_data成員用于存放socket地址值。但是,不同的協(xié)議族的地址值具有不同的含義和長度
可以看到,14個字節(jié)只能裝下 IPv4地址,沒辦法裝下IPv6的地址。因此,該結(jié)構(gòu)體表示方式已經(jīng)被廢掉,Linux定義了下面這個新的通用的socket地址結(jié)構(gòu)體,這個結(jié)構(gòu)體不僅提供了足夠大的空間用于存放地址值,而且是內(nèi)存對齊的【內(nèi)存對齊可以加快CPU訪問速度】
這個結(jié)構(gòu)體定義在:/usr/include/linux/in.h
#include <bits/socket.h>
struct sockaddr_storage
{
sa_family_t sa_family;
unsigned long int __ss_align; //不用管,用來作內(nèi)存對齊的
char __ss_padding[ 128 - sizeof(__ss_align) ];
};
typedef unsigned short int sa_family_t;
專用socket地址
很多網(wǎng)絡(luò)編程函數(shù)誕生早于IPv4協(xié)議(用自定義的協(xié)議咯,雙方共同約定一個規(guī)則),那時候都是使用struck socketaddr結(jié)構(gòu)體,*為了向前兼容,現(xiàn)在在socketaddr退化成了 (void )的作用,傳遞一個地址給函數(shù),至于這個函數(shù)是sockaddr_in還是sockaddr_in6,由地址族確定,然后函數(shù)內(nèi)部再強(qiáng)制類型轉(zhuǎn)化為所需的地址類型。
主要要記住的就是下圖中的第二個struct sockaddr_in
UNIX 本地域協(xié)議族使用如下專用的 socket 地址結(jié)構(gòu)體:
#include <sys/un.h>
struct sockaddr_un
{
sa_family_t sin_family;
char sun_path[108];
};
TCP/IP 協(xié)議族有 sockaddr_in 和 sockaddr_in6 兩個專用的 socket 地址結(jié)構(gòu)體,它們分別用于 IPv4 和 IPv6:
#include <netinet/in.h>
struct sockaddr_in
{
sa_family_t sin_family; /* __SOCKADDR_COMMON(sin_) */
in_port_t sin_port; /* Port number. 2個字節(jié)的端口號 */
struct in_addr sin_addr; /* Internet address. 4個字節(jié)的ip地址 */
/* Pad to size of `struct sockaddr'. 剩余填充的部分*/
unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) - sizeof (struct in_addr)];
};
struct in_addr
{
in_addr_t s_addr;
};
struct sockaddr_in6
{
sa_family_t sin6_family;
in_port_t sin6_port; /* Transport layer port # */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* IPv6 scope-id */
};
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))
所有專用 socket 地址(以及 sockaddr_storage)類型的變量在實際使用時都需要轉(zhuǎn)化為通用 socket 地 址類型 sockaddr(強(qiáng)制轉(zhuǎn)化即可),因為所有 socket 編程接口使用的地址參數(shù)類型都是 sockaddr。
4、IP地址轉(zhuǎn)換函數(shù)
人們習(xí)慣用可讀性好的字符串來表示IP地址,比如用點(diǎn)分十進(jìn)制字符串表示IPV4地址,以及用十六進(jìn)制字符串表示IPv6地址,但編程中我們需要先把他們轉(zhuǎn)化為整數(shù)(二進(jìn)制)方能使用。而記錄日志相反,我們需要把整數(shù)表示的IP地址轉(zhuǎn)化為可讀的字符串。
p:點(diǎn)分十進(jìn)制的IP字符串
n:表示network,網(wǎng)絡(luò)字節(jié)序的整數(shù)
#include <arpa/inet.h>
將IP地址從字符串形式轉(zhuǎn)化為二進(jìn)制整數(shù)形式
int inet_pton(int af,const char *src,void *dst);
af:地址族: AF_INET AF_INET6
src:需要轉(zhuǎn)換的點(diǎn)分十進(jìn)制的IP字符串
dst:轉(zhuǎn)換后的結(jié)果保存在這個里面
將網(wǎng)絡(luò)字節(jié)序的整數(shù),轉(zhuǎn)換成點(diǎn)分十進(jìn)制的IP地址字符串
const char *inet_ntop(int af,const void *src,char *dst,socklen_t size);
af:AF_INET AF_INE6
src: 要轉(zhuǎn)換的ip的整數(shù)的地址
dst: 轉(zhuǎn)換成的IP地址字符串保存的地方
size:第三個參數(shù)的大?。〝?shù)組的大?。?
返回值:返回轉(zhuǎn)換后的數(shù)據(jù)的地址(字符串),和 dst 是一樣的
點(diǎn)分十進(jìn)制 ---> 網(wǎng)絡(luò)字節(jié)序 inet_pton
網(wǎng)絡(luò)字節(jié)序 ---> 點(diǎn)分十進(jìn)制 inet_ntop
代碼舉例:
/*
#include <arpa/inet.h>
// p:點(diǎn)分十進(jìn)制的IP字符串,n:表示network,網(wǎng)絡(luò)字節(jié)序的整數(shù)
int inet_pton(int af, const char *src, void *dst);
af:地址族: AF_INET AF_INET6
src:需要轉(zhuǎn)換的點(diǎn)分十進(jìn)制的IP字符串
dst:轉(zhuǎn)換后的結(jié)果保存在這個里面
// 將網(wǎng)絡(luò)字節(jié)序的整數(shù),轉(zhuǎn)換成點(diǎn)分十進(jìn)制的IP地址字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
af:地址族: AF_INET AF_INET6
src: 要轉(zhuǎn)換的ip的整數(shù)的地址
dst: 轉(zhuǎn)換成IP地址字符串保存的地方
size:第三個參數(shù)的大?。〝?shù)組的大?。? 返回值:返回轉(zhuǎn)換后的數(shù)據(jù)的地址(字符串),和 dst 是一樣的
*/
#include <stdio.h>
#include <arpa/inet.h>
int main() {
// 創(chuàng)建一個ip字符串,點(diǎn)分十進(jìn)制的IP地址字符串
char buf[] = "192.168.1.4";
unsigned int num = 0;
// 將點(diǎn)分十進(jìn)制的IP字符串轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序的整數(shù)
inet_pton(AF_INET, buf, &num);
unsigned char * p = (unsigned char *)#
printf("%d %d %d %d\n", *p, *(p+1), *(p+2), *(p+3));
// 將網(wǎng)絡(luò)字節(jié)序的IP整數(shù)轉(zhuǎn)換成點(diǎn)分十進(jìn)制的IP字符串
char ip[16] = ""; //字符串IP地址四段,每段最多三個字節(jié),加上3個“.”,再加一個字符串結(jié)束符
const char * str = inet_ntop(AF_INET, &num, ip, 16);
printf("str : %s\n", str);
printf("ip : %s\n", ip);
printf("%d\n", ip == str);
return 0;
}
5、套接字函數(shù)
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>//包含了這個頭文件,上面兩個就可以省略
int socket(int domain,int type,int protoco1);
- 功能:創(chuàng)建一個套接字
- 參數(shù):
- domain:協(xié)議族
AF_INET:ipv4
AF_INET6:ipv6
AF_UNIX,AF_LOCAL:本地套接字通信(進(jìn)程間通信)
- type:通信過程中使用的協(xié)議類型
SOCK_STREAM:流式協(xié)議(TCP等)
SOCK_DGRAM:報式協(xié)議(UDP等)
- protocol:具體的一個協(xié)議。一般寫0
- SOCK_STREAM:流式協(xié)議默認(rèn)使用TCP
- SOCK_DGRAM:報式協(xié)議默認(rèn)使用UDP
- 返回值:
- 成功:返回文件描述符,操作的就是內(nèi)核緩沖區(qū)
- 失?。?span id="n5n3t3z" class="token operator">-1
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
- 功能:綁定,將fd和本地的IP+端口進(jìn)行綁定
- 參數(shù):
- socket:通過socket函數(shù)得到的文件描述符
- addr:需要綁定的socket地址,這個地址封裝了ip和端口號的信息
- addr len:第二個參數(shù)結(jié)構(gòu)體占的內(nèi)存大小
- 返回值:成功返回0,失敗返回-1
int listen(int sockfd,int backlog);// /proc/sys/net/cor e/somaxconn
- 功能:監(jiān)聽這個socket上的連接
- 參數(shù):
- sockfd:通過socket()函數(shù)得到的文件描述符
- backlog:未連接的和已連接的和的最大值,超過該設(shè)定的最大值的連接會被舍棄掉。但該設(shè)定值不能超過/proc/sys/net/cor e/somaxconn這個文件里的數(shù)值
int accept(int sockfd,struct sockaddr *addr ,sock1en_t *addrlen);
- 功能:接收客戶端連接,默認(rèn)是一個阻塞的函數(shù),阻塞等待客戶的連接
- 參數(shù):
- sockfd:用于監(jiān)聽的文件描述符
- addr:傳出參數(shù),記錄了連接成功后客戶端的地址信息(IP和端口號)
- addrlen:指定第二個參數(shù)的對應(yīng)的內(nèi)存的大小
- 返回值:
- 成功:返回用于通信的文件描述符
- -1:失敗
int connect(int sockfd,const struct sockaddr *addr,socklen_t addr1en);
- 功能:客戶端連接服務(wù)器
- 參數(shù):
- sockfd:用于通信的文件描述符
- addr:客戶端要連接的服務(wù)器的地址信息
- addrlen:第二個參數(shù)的內(nèi)存大小
- 返回值:成功返回0,時報返回-1
ssize_t write(int fd,const void *buf, size_t count);
ssize_t read(int fd,void *buf, size_t count);
6、TCP通信實現(xiàn)(服務(wù)器端和客戶端)
注意,該程序是根據(jù)TCP通信過程中服務(wù)器的接收信息的步驟寫的,可以先參考該篇文章。
服務(wù)器端
// TCP 通信的服務(wù)器端
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main() {
// 1.創(chuàng)建socket(用于監(jiān)聽的套接字)
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1) {
perror("socket");
exit(-1);
}
// 2.綁定
struct sockaddr_in saddr; //這個結(jié)構(gòu)體本文章的上半部分有詳細(xì)的介紹,不了解可以去看看
saddr.sin_family = AF_INET;
// inet_pton(AF_INET, "192.168.193.128", &saddr.sin_addr.s_addr);
saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
saddr.sin_port = htons(9999);
int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
if(ret == -1) {
perror("bind");
exit(-1);
}
// 3.監(jiān)聽
ret = listen(lfd, 8);
if(ret == -1) {
perror("listen");
exit(-1);
}
// 4.接收客戶端連接
struct sockaddr_in clientaddr;
int len = sizeof(clientaddr);
int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len);
if(cfd == -1) {
perror("accept");
exit(-1);
}
// 輸出客戶端的信息
char clientIP[16];
inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
unsigned short clientPort = ntohs(clientaddr.sin_port);
printf("client ip is %s, port is %d\n", clientIP, clientPort);
// 5.通信
char recvBuf[1024] = {0};
while(1) {
// 獲取客戶端的數(shù)據(jù)
int num = read(cfd, recvBuf, sizeof(recvBuf));
if(num == -1) {
perror("read");
exit(-1);
} else if(num > 0) {
printf("recv client data : %s\n", recvBuf);
} else if(num == 0) {
// 表示客戶端斷開連接
printf("clinet closed...");
break;
}
char * data = "hello,i am server";
// 給客戶端發(fā)送數(shù)據(jù)
write(cfd, data, strlen(data));
}
// 關(guān)閉文件描述符
close(cfd);
close(lfd);
return 0;
}
問題:假設(shè)服務(wù)端先調(diào)用一次read把客戶端文件描述符中的內(nèi)容讀完了,這時候客戶端沒有往描述符中寫數(shù)據(jù)了,但是也沒有斷開連接,那此時服務(wù)器第二次調(diào)用read會返回什么?
答:讀管道的特點(diǎn),當(dāng)管道中無數(shù)據(jù):1、寫端被全部關(guān)閉,read返回0(相當(dāng)于讀到文件的末尾)2、寫端沒有完全關(guān)閉,read阻塞等待。參考我的這篇文章【Linux】管道的讀寫特點(diǎn)和管道設(shè)置為非阻塞
客戶端
// TCP通信的客戶端
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main() {
// 1.創(chuàng)建套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1) {
perror("socket");
exit(-1);
}
// 2.連接服務(wù)器端,注意是要服務(wù)器的ip地址和端口
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
inet_pton(AF_INET, "192.168.177.146", &serveraddr.sin_addr.s_addr);
serveraddr.sin_port = htons(9999);
int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if(ret == -1) {
perror("connect");
exit(-1);
}
// 3. 通信
char recvBuf[1024] = {0};
while(1) {
char * data = "hello,i am client";
// 給客戶端發(fā)送數(shù)據(jù)
write(fd, data , strlen(data));
sleep(1);
int len = read(fd, recvBuf, sizeof(recvBuf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len > 0) {
printf("recv server data : %s\n", recvBuf);
} else if(len == 0) {
// 表示服務(wù)器端斷開連接
printf("server closed...");
break;
}
}
// 關(guān)閉連接
close(fd);
return 0;
}
客戶端第21行中的ip地址應(yīng)該寫自己主機(jī)的ip地址,我的主機(jī)的ip地址為192.168.177.146
分別將兩個文件編譯并執(zhí)行。得到結(jié)果如下:
可以看到,在服務(wù)器的執(zhí)行程序中,打印出了客戶端的ip為192.168.177.146,端口隨機(jī)分配為35302
注意:要先啟動服務(wù)端,再啟動客戶端。
作業(yè):把服務(wù)器改為回頻服務(wù)器,也就是服務(wù)器把客戶端發(fā)送來的數(shù)據(jù)再發(fā)送回去。把客戶端改為從鍵盤輸入數(shù)據(jù)并發(fā)送給服務(wù)端。所以最后的效果就是,我從鍵盤輸入,客戶端發(fā)送給服務(wù)端,服務(wù)端再把相同的內(nèi)容發(fā)送回來。
服務(wù)端:
// TCP 通信的服務(wù)器端
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main() {
// 1.創(chuàng)建socket(用于監(jiān)聽的套接字)
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1) {
perror("socket");
exit(-1);
}
// 2.綁定
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
// inet_pton(AF_INET, "192.168.193.128", &saddr.sin_addr.s_addr);
saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
saddr.sin_port = htons(9999);
int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
if(ret == -1) {
perror("bind");
exit(-1);
}
// 3.監(jiān)聽
ret = listen(lfd, 8);
if(ret == -1) {
perror("listen");
exit(-1);
}
// 4.接收客戶端連接
struct sockaddr_in clientaddr;
int len = sizeof(clientaddr);
int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len);
if(cfd == -1) {
perror("accept");
exit(-1);
}
// 輸出客戶端的信息
char clientIP[16];
inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
unsigned short clientPort = ntohs(clientaddr.sin_port);
printf("client ip is %s, port is %d\n", clientIP, clientPort);
// 5.通信
char recvBuf[1024] = {0};
while(1) {
memset(recvBuf, 0, 1024);
// 獲取客戶端的數(shù)據(jù)
int num = read(cfd, recvBuf, sizeof(recvBuf));
if(num == -1) {
perror("read");
exit(-1);
} else if(num > 0) {
printf("recv client data : %s\n", recvBuf);
} else if(num == 0) {
// 表示客戶端斷開連接
printf("clinet closed...");
break;
}
char * data = recvBuf;;
// 給客戶端發(fā)送數(shù)據(jù)
write(cfd, data, strlen(data));
}
// 關(guān)閉文件描述符
close(cfd);
close(lfd);
return 0;
}
客戶端:文章來源:http://www.zghlxwxcb.cn/news/detail-492778.html
// TCP通信的客戶端
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main() {
// 1.創(chuàng)建套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1) {
perror("socket");
exit(-1);
}
// 2.連接服務(wù)器端,注意是要服務(wù)器的ip地址和端口
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
inet_pton(AF_INET, "192.168.177.146", &serveraddr.sin_addr.s_addr);
serveraddr.sin_port = htons(9999);
int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if(ret == -1) {
perror("connect");
exit(-1);
}
// 3. 通信
char recvBuf[1024] = {0};
while(1) {
char data[1024];
memset(data, 0, 1024);
printf("請輸入發(fā)送數(shù)據(jù):\n");
scanf("%s", data);
// 給客戶端發(fā)送數(shù)據(jù)
write(fd, data , strlen(data));
sleep(1);
memset(recvBuf, 0, 1024);
int len = read(fd, recvBuf, sizeof(recvBuf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len > 0) {
printf("recv server data : %s\n", recvBuf);
} else if(len == 0) {
// 表示服務(wù)器端斷開連接
printf("server closed...");
break;
}
}
// 關(guān)閉連接
close(fd);
return 0;
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-492778.html
到了這里,關(guān)于【Linux】socket 編程(socket套接字介紹、字節(jié)序、socket地址、IP地址轉(zhuǎn)換函數(shù)、套接字函數(shù)、TCP通信實現(xiàn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!