一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Socket編程的目的就是如何實(shí)現(xiàn)這兩端之間的通信

GReq_mcu168 ? 來(lái)源:玩轉(zhuǎn)單片機(jī) ? 2020-06-28 17:15 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

一、什么是socket?

Socket的英文原義是“孔”或“插座”。在編程中,Socket被稱做套接字,是網(wǎng)絡(luò)通信中的一種約定。Socket編程的應(yīng)用無(wú)處不在,我們平時(shí)用的QQ、微信、瀏覽器等程序,都與Socket編程有關(guān)。我們平時(shí)使用瀏覽器查資料,這個(gè)過程的技術(shù)原理是怎樣的呢?

我們平時(shí)使用瀏覽器,大致就是這樣的一個(gè)過程。這里有兩個(gè)重要的名詞:服務(wù)端與客戶端。

Socket編程的目的就是如何實(shí)現(xiàn)這兩端之間的通信。

1、Socket編程在嵌入式中也很重要

Socket編程不僅僅在互聯(lián)網(wǎng)方面很重要,在我們的嵌入式方面也是非常的重要,因?yàn)楝F(xiàn)在很多電子設(shè)備都趨向于聯(lián)網(wǎng)。比如很多嵌入式工作的招聘要求都會(huì)有這一條要求:

說(shuō)一點(diǎn)題外話,還在學(xué)校的朋友,如果感覺到很迷茫,不知道學(xué)什么的時(shí)候,可以上招聘網(wǎng)站上看看自己未來(lái)工作相關(guān)的職位的任職要求,這樣就可以總結(jié)自己的一些不足、比較有針對(duì)性的去學(xué)習(xí)。

二、Socket編程中的幾個(gè)重要概念

Socket編程用于解決我們客戶端與服務(wù)端之間通信的問題。我們平時(shí)多多少少都有聽過IP地址、端口、TCP協(xié)議、UDP協(xié)議等概念,這些都與Socket編程中相關(guān),想要知道怎么用起來(lái),當(dāng)然得先了解它們的一些介紹。下面看一下這些專業(yè)術(shù)語(yǔ)的一些要點(diǎn)介紹:

1、什么是IP地址?

IP地址(InternetProtocolAddress)是指互聯(lián)網(wǎng)協(xié)議地址,又譯為網(wǎng)際協(xié)議地址。IP地址被用來(lái)給Internet上的電腦一個(gè)編號(hào)。我們可以把“個(gè)人電腦”比作“一臺(tái)電話”,那么“IP地址”就相當(dāng)于“電話號(hào)碼”。若計(jì)算機(jī)1知道計(jì)算機(jī)2的IP地址,則計(jì)算機(jī)1就能訪問計(jì)算機(jī)2。

IP地址是一個(gè)32位的二進(jìn)制數(shù),通常被分割為4個(gè)“8位二進(jìn)制數(shù)”(也就是4個(gè)字節(jié))。IP地址通常用點(diǎn)分十進(jìn)制表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之間的十進(jìn)制整數(shù)。例:點(diǎn)分十進(jìn)IP地址(100.4.5.6),實(shí)際上是32位二進(jìn)制數(shù)(01100100.00000100.00000101.00000110)。

IP地址有IPv4與IPv6之分,現(xiàn)在用得較多的是IPv4。其中,有一個(gè)特殊的IP地址需要我們記?。?27.0.0.1,這是回送地址,即本地機(jī),一般用來(lái)測(cè)試使用。后邊我們的實(shí)例中會(huì)用到。

關(guān)于IP地址還有很多知識(shí)要點(diǎn),但是對(duì)于在Socket編程中的應(yīng)用,我們暫且知道這么多就可以。

2、什么是TCP/IP端口?

上一點(diǎn)中我們提到,若計(jì)算機(jī)1知道計(jì)算機(jī)2的IP地址,則計(jì)算機(jī)1就能訪問計(jì)算機(jī)2。但是,我們要訪問計(jì)算機(jī)2中的不同的應(yīng)用軟件,則還得需要一個(gè)信息:端口。端口使用16bit進(jìn)行編號(hào),即其范圍為:0~65536。但0~1023的端口一般由系統(tǒng)分配給特定的服務(wù)程序,例如 Web 服務(wù)的端口號(hào)為 80,F(xiàn)TP 服務(wù)的端口號(hào)為 21等。

3、什么是協(xié)議?

協(xié)議(Protocol)是通信雙方進(jìn)行數(shù)據(jù)交互的一種約定。如TCP、UDP協(xié)議:

(1)TCP協(xié)議

TCP(Transmission Control Protocol 傳輸控制協(xié)議)是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議,數(shù)據(jù)可以準(zhǔn)確發(fā)送,數(shù)據(jù)丟失會(huì)重發(fā)。TCP協(xié)議常用于web應(yīng)用中。

TCP連接(三次握手)

TCP傳輸起始時(shí),客戶端、服務(wù)端要完成三次數(shù)據(jù)交互工作才能建立連接,常稱為三次握手。可形象比喻為如下對(duì)話:

客戶端:服務(wù)端您好,我有數(shù)據(jù)要發(fā)給你,請(qǐng)求您開通訪問權(quán)限。

服務(wù)端:客戶端您好,已給您開通權(quán)限,您可以發(fā)送數(shù)據(jù)了。

客戶端:收到,謝謝。

具體示意圖為:

這里的SYN和ACK是都是標(biāo)志位,其中SYN代表新建一個(gè)連接,ACK代表確認(rèn)。其中m、n都是隨機(jī)數(shù)。具體說(shuō)明如:

第一次握手:SYN標(biāo)志位被置位,客戶端向服務(wù)端發(fā)送一個(gè)隨機(jī)數(shù)m。

第二次握手:ACK、SYN標(biāo)志位被置位。服務(wù)端向客戶端發(fā)送m+1表示確認(rèn)剛才收到的數(shù)據(jù),同時(shí)向客戶端發(fā)送一個(gè)隨機(jī)數(shù)n。

第三次握手:ACK標(biāo)志被置位??蛻舳讼蚍?wù)端發(fā)送n+1表示確認(rèn)收到數(shù)據(jù)。

TCP斷開(四次揮手)

TCP斷開連接時(shí),客戶端、服務(wù)端要完成四次數(shù)據(jù)交互工作才能建立連接,常稱為四次揮手??尚蜗蟊扔鳛槿缦聦?duì)話:

客戶端:服務(wù)端您好,我發(fā)送數(shù)據(jù)完畢了,即將和您斷開連接。

服務(wù)端:客戶端您好,我稍稍準(zhǔn)備一下,再給您斷開

服務(wù)端:客戶端您好,我準(zhǔn)備好了,您可以斷開連接了。

客戶端:好的,合作愉快!

具體示意圖為:

這里的FIN也是一個(gè)標(biāo)志位,代表斷開連接。具體說(shuō)明類似三次握手。

為什么建立連接只需要三次數(shù)據(jù)交互,而斷開連接需要四次呢?

建立連接時(shí),服務(wù)端在監(jiān)聽狀態(tài)下,收到建立連接請(qǐng)求的SYN報(bào)文后,把ACK和SYN放在一個(gè)報(bào)文里發(fā)送給客戶端。

而關(guān)閉連接時(shí),當(dāng)收到對(duì)方的FIN報(bào)文時(shí),僅僅表示對(duì)方不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù),己方也未必全部數(shù)據(jù)都發(fā)送給對(duì)方了,所以己方可以立即close,也可以發(fā)送一些數(shù)據(jù)給對(duì)方后,再發(fā)送FIN報(bào)文給對(duì)方來(lái)表示同意現(xiàn)在關(guān)閉連接,因此,己方ACK和FIN一般都會(huì)分開發(fā)送。

(2)UDP協(xié)議

UDP(User Datagram Protocol, 用戶數(shù)據(jù)報(bào)協(xié)議)是一種無(wú)連接的傳輸層協(xié)議,提供面向事務(wù)的簡(jiǎn)單不可靠信息傳送服務(wù),可以保證通訊效率,傳輸延時(shí)小。例如視頻聊天應(yīng)用中用的就是UDP協(xié)議,這樣可以保證及時(shí)丟失少量數(shù)據(jù),視頻的顯示也不受很大影響。

4、什么是協(xié)議族?

協(xié)議族是多個(gè)協(xié)議的統(tǒng)稱。比如我們的TCP/IP協(xié)議族,其不僅僅是TCP協(xié)議、IP協(xié)議,而是多個(gè)協(xié)議的集合,其包含IP、TCP、UDP、FTP、SMTP等協(xié)議。

三、socket編程的API接口

1、Linux下的socket API接口

(1)創(chuàng)建socket:socket()函數(shù)

函數(shù)原型:

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

af參數(shù):af 為地址族(Address Family),也就是 IP 地址類型,常用的有 AFINET 和 AFINET6,其前綴也可以是PF(Protocol Family),即PFINET 和 PFINET6。

type參數(shù):type 為數(shù)據(jù)傳輸方式,常用的有 面向連接(SOCK_STREAM)方式(即TCP) 和 無(wú)連接(SOCK_DGRAM)的方式(即UDP)。

protocol參數(shù):protocol 表示傳輸協(xié)議,常用的有IPPROTO_TCP和IPPTOTO_UDP,分別表示 TCP 傳輸協(xié)議和 UDP 傳輸協(xié)議。

使用示例:

創(chuàng)建TCP套接字:

int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

創(chuàng)建UDP套接字:

int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

(2)綁定套接字:bind()函數(shù)

函數(shù)原型:

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

sock參數(shù):sock 為 socket 文件描述符。

addr參數(shù):addr 為 sockaddr 結(jié)構(gòu)體變量的指針。

addrlen參數(shù):addrlen 為 addr 變量的大小,可由 sizeof() 計(jì)算得出。

使用示例:

將創(chuàng)建的套接字ServerSock與本地IP127.0.0.1、端口1314進(jìn)行綁定:

/* 創(chuàng)建服務(wù)端socket */

int ServerSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

/* 設(shè)置服務(wù)端信息 */

struct sockaddr_in ServerSockAddr;

memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 給結(jié)構(gòu)體ServerSockAddr清零

ServerSockAddr.sin_family = PF_INET; // 使用IPv4地址

ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 本機(jī)IP地址

ServerSockAddr.sin_port = htons(1314); // 端口

/* 綁定套接字 */

bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));

其中structsockaddr_in類型的結(jié)構(gòu)體變量用于保存IPv4的IP信息。若是IPv6,則有對(duì)應(yīng)的結(jié)構(gòu)體:

struct sockaddr_in6

{

sa_family_t sin6_family; // 地址類型,取值為AF_INET6

in_port_t sin6_port; // 16位端口號(hào)

uint32_t sin6_flowinfo; // IPv6流信息

struct in6_addr sin6_addr; // 具體的IPv6地址

uint32_t sin6_scope_id; // 接口范圍ID

};

(3)建立連接:connect()函數(shù)

函數(shù)原型:

int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);

參數(shù)與bind()的參數(shù)類似。

使用示例:

int ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));

(4)監(jiān)聽:listen()函數(shù)

函數(shù)原型:

int listen(int sock, int backlog);

sock參數(shù):sock 為需要進(jìn)入監(jiān)聽狀態(tài)的套接字。

backlog參數(shù):backlog 為請(qǐng)求隊(duì)列的最大長(zhǎng)度。

使用示例:

/* 進(jìn)入監(jiān)聽狀態(tài) */

listen(ServerSock, 10);

(5)接收請(qǐng)求:accept()函數(shù)

函數(shù)原型:

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

sock參數(shù):sock 為服務(wù)器端套接字。

addr參數(shù):addr 為 sockaddr_in 結(jié)構(gòu)體變量。

addrlen參數(shù):addrlen 為參數(shù) addr 的長(zhǎng)度,可由 sizeof() 求得。

返回值:一個(gè)新的套接字,用于與客戶端通信。

使用示例:

/* 監(jiān)聽客戶端請(qǐng)求,accept函數(shù)返回一個(gè)新的套接字,發(fā)送和接收都是用這個(gè)套接字 */

int ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &len);

(6)關(guān)閉:close()函數(shù)

函數(shù)原型:

int close(int fd);

fd:要關(guān)閉的文件描述符。

使用示例:

close(ServerSock);

(7)數(shù)據(jù)的接收和發(fā)送

數(shù)據(jù)收發(fā)函數(shù)有幾組:

read()/write()

recv()/send()

readv()/writev()

recvmsg()/sendmsg()

recvfrom()/sendto()

函數(shù)原型:

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

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

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,

const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,

struct sockaddr *src_addr, socklen_t *addrlen);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

這里介紹一下recv()/send()、recvfrom()/sendto()。

recv()函數(shù):

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

sockfd參數(shù):sockfd為要接收數(shù)據(jù)的套接字。

buf參數(shù):buf 為要接收的數(shù)據(jù)的緩沖區(qū)地址。

len參數(shù):len 為要接收的數(shù)據(jù)的字節(jié)數(shù)。

flags參數(shù):flags 為接收數(shù)據(jù)時(shí)的選項(xiàng),常設(shè)為0。

send()函數(shù):

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

sockfd參數(shù):sockfd為要發(fā)送數(shù)據(jù)的套接字。

buf參數(shù):buf 為要發(fā)送的數(shù)據(jù)的緩沖區(qū)地址。

len參數(shù):len 為要發(fā)送的數(shù)據(jù)的字節(jié)數(shù)。

flags參數(shù):flags 為發(fā)送數(shù)據(jù)時(shí)的選項(xiàng),常設(shè)為0。

recvfrom()函數(shù):

ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockadr *from, socklen_t *addrlen);

sock:用于接收UDP數(shù)據(jù)的套接字;

buf:保存接收數(shù)據(jù)的緩沖區(qū)地址;

nbytes:可接收的最大字節(jié)數(shù)(不能超過buf緩沖區(qū)的大小);

flags:可選項(xiàng)參數(shù),若沒有可傳遞0;

from:存有發(fā)送端地址信息的sockaddr結(jié)構(gòu)體變量的地址;

addrlen:保存參數(shù) from 的結(jié)構(gòu)體變量長(zhǎng)度的變量地址值。

sendto()函數(shù):

ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);

sock:用于傳輸U(kuò)DP數(shù)據(jù)的套接字;

buf:保存待傳輸數(shù)據(jù)的緩沖區(qū)地址;

nbytes:帶傳輸數(shù)據(jù)的長(zhǎng)度(以字節(jié)計(jì));

flags:可選項(xiàng)參數(shù),若沒有可傳遞0;

to:存有目標(biāo)地址信息的 sockaddr 結(jié)構(gòu)體變量的地址;

addrlen:傳遞給參數(shù) to 的地址值結(jié)構(gòu)體變量的長(zhǎng)度。

2、windows下的socket API接口

跟Linux下的差不多:

SOCKET socket(int af, int type, int protocol);

int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);

int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);

int listen(SOCKET sock, int backlog);

SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);

int closesocket( SOCKET s);

int send(SOCKET sock, const char *buf, int len, int flags);

int recv(SOCKET sock, char *buf, int len, int flags);

int recvfrom(SOCKET sock, char *buf, int nbytes, int flags, const struct sockaddr *from, int *addrlen);

int sendto(SOCKET sock, const char *buf, int nbytes, int flags, const struct sockadr *to, int addrlen);

3、TCP、UDP通信的socket編程過程圖

(1)TCP通信socket編程過程

(2)UDP通信socket編程過程

四、socket的應(yīng)用實(shí)例

1、基于TCP的本地客戶端、服務(wù)端信息交互實(shí)例

本例的例子實(shí)現(xiàn)的功能為:本地TCP客戶端往本地TCP服務(wù)端發(fā)送數(shù)據(jù),TCP服務(wù)端收到數(shù)據(jù)則會(huì)打印輸出,同時(shí)把原數(shù)據(jù)返回給TCP客戶端。這個(gè)例子類似于我們?cè)谧?a target="_blank">單片機(jī)的串口實(shí)驗(yàn)時(shí),串口上位機(jī)往我們的單片機(jī)發(fā)送數(shù)據(jù),單片機(jī)收到數(shù)據(jù)則把該數(shù)據(jù)原樣返回給上位機(jī)。

(1)windows的程序:

服務(wù)端程序tcp_server.c:

#include

#include

#define BUF_LEN 100

int main(void)

{

WSADATA wd;

SOCKET ServerSock, ClientSock;

char Buf[BUF_LEN] = {0};

SOCKADDR ClientAddr;

SOCKADDR_IN ServerSockAddr;

int addr_size = 0, recv_len = 0;

/* 初始化操作sock需要的DLL */

WSAStartup(MAKEWORD(2,2),&wd);

/* 創(chuàng)建服務(wù)端socket */

if (-1 == (ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))

{

printf("socket error! ");

exit(1);

}

/* 設(shè)置服務(wù)端信息 */

memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 給結(jié)構(gòu)體ServerSockAddr清零

ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址

ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 本機(jī)IP地址

ServerSockAddr.sin_port = htons(1314); // 端口

/* 綁定套接字 */

if (-1 == bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR)))

{

printf("bind error! ");

exit(1);

}

/* 進(jìn)入監(jiān)聽狀態(tài) */

if (-1 == listen(ServerSock, 10))

{

printf("listen error! ");

exit(1);

}

addr_size = sizeof(SOCKADDR);

while (1)

{

/* 監(jiān)聽客戶端請(qǐng)求,accept函數(shù)返回一個(gè)新的套接字,發(fā)送和接收都是用這個(gè)套接字 */

if (-1 == (ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &addr_size)))

{

printf("socket error! ");

exit(1);

}

/* 接受客戶端的返回?cái)?shù)據(jù) */

int recv_len = recv(ClientSock, Buf, BUF_LEN, 0);

printf("客戶端發(fā)送過來(lái)的數(shù)據(jù)為:%s ", Buf);

/* 發(fā)送數(shù)據(jù)到客戶端 */

send(ClientSock, Buf, recv_len, 0);

/* 關(guān)閉客戶端套接字 */

closesocket(ClientSock);

/* 清空緩沖區(qū) */

memset(Buf, 0, BUF_LEN);

}

/*如果有退出循環(huán)的條件,這里還需要清除對(duì)socket庫(kù)的使用*/

/* 關(guān)閉服務(wù)端套接字 */

//closesocket(ServerSock);

/* WSACleanup();*/

return 0;

}

客戶端程序tcp_client.c:

#include

#include

#define BUF_LEN 100

int main(void)

{

WSADATA wd;

SOCKET ClientSock;

char Buf[BUF_LEN] = {0};

SOCKADDR_IN ServerSockAddr;

/* 初始化操作sock需要的DLL */

WSAStartup(MAKEWORD(2,2),&wd);

/* 向服務(wù)器發(fā)起請(qǐng)求 */

memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));

ServerSockAddr.sin_family = AF_INET;

ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

ServerSockAddr.sin_port = htons(1314);

while (1)

{

/* 創(chuàng)建客戶端socket */

if (-1 == (ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))

{

printf("socket error! ");

exit(1);

}

if (-1 == connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR)))

{

printf("connect error! ");

exit(1);

}

printf("請(qǐng)輸入一個(gè)字符串,發(fā)送給服務(wù)端:");

gets(Buf);

/* 發(fā)送數(shù)據(jù)到服務(wù)端 */

send(ClientSock, Buf, strlen(Buf), 0);

/* 接受服務(wù)端的返回?cái)?shù)據(jù) */

recv(ClientSock, Buf, BUF_LEN, 0);

printf("服務(wù)端發(fā)送過來(lái)的數(shù)據(jù)為:%s ", Buf);

memset(Buf, 0, BUF_LEN); // 重置緩沖區(qū)

closesocket(ClientSock); // 關(guān)閉套接字

}

// WSACleanup(); /*如果有退出循環(huán)的條件,這里還需要清除對(duì)socket庫(kù)的使用*/

return 0;

}

我們上邊的IP地址概念那一部分中,有強(qiáng)調(diào)127.0.0.1這個(gè)IP是一個(gè)特殊的IP地址,這是回送地址,即本地機(jī),一般用來(lái)測(cè)試使用。這個(gè)例子中我們就用到了。此外,端口我們?cè)O(shè)置為1314,這是隨意設(shè)置的,只要范圍在1024~65536之間就可以。

本文使用的是gcc編譯器編譯(關(guān)于gcc編譯器的相關(guān)介紹可查看往期筆記:【C語(yǔ)言筆記】使用notepad++、MinGW來(lái)開發(fā)C程序及【C語(yǔ)言筆記】windows命令行下編譯C程序),編譯命令如下:

gcc tcp_client.c -o tcp_client.exe -lwsock32

gcc tcp_server.c -o tcp_server.exe -lwsock32

這里必須要加-lwsock32這個(gè)參數(shù)用于鏈接windows下socket編程必須的winsock2這個(gè)庫(kù)。若是使用集成開發(fā)環(huán)境,則需要把wsock32.lib放在工程目錄下,并在我們代碼中#include下面加上一行#pragmacomment(lib,"ws2_32.lib")代碼(這種情況本人未驗(yàn)證,有興趣的朋友可嘗試)。

實(shí)驗(yàn)現(xiàn)象:

先啟動(dòng)服務(wù)端程序tcp_server.exe,再啟動(dòng)客戶端程序tcp_client.exe,并在客戶端中輸入字符串,則當(dāng)服務(wù)端會(huì)接收到字符串時(shí)會(huì)打印輸出,與此同時(shí)也會(huì)往客戶端返回相同的數(shù)據(jù):

動(dòng)圖:

(2)Linux的程序:

在linux下,“一切都是文件”,所以這里我們的套接字也當(dāng)做文件來(lái)看待。

服務(wù)端程序linux_tcp_server.c:

#include

#include

#include

#include

#include

#include

#include

#define BUF_LEN 100

int main(void)

{

int ServerFd, ClientFd;

char Buf[BUF_LEN] = {0};

struct sockaddr ClientAddr;

int addr_len = 0, recv_len = 0;

struct sockaddr_in ServerSockAddr;

int optval = 1;

/* 創(chuàng)建服務(wù)端文件描述符 */

if (-1 == (ServerFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))

{

printf("socket error! ");

exit(1);

}

/* 設(shè)置服務(wù)端信息 */

memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 給結(jié)構(gòu)體ServerSockAddr清零

ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址

ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 自動(dòng)獲取IP地址

ServerSockAddr.sin_port = htons(6666); // 端口

// 設(shè)置地址和端口號(hào)可以重復(fù)使用

if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)

{

printf("setsockopt error! ");

exit(1);

}

/* 綁定操作,綁定前加上上面的socket屬性可重復(fù)使用地址 */

if (-1 == bind(ServerFd, (struct sockaddr*)&ServerSockAddr, sizeof(struct sockaddr)))

{

printf("bind error! ");

exit(1);

}

/* 進(jìn)入監(jiān)聽狀態(tài) */

if (-1 == (listen(ServerFd, 10)))

{

printf("listen error! ");

exit(1);

}

addr_len = sizeof(struct sockaddr);

while (1)

{

/* 監(jiān)聽客戶端請(qǐng)求,accept函數(shù)返回一個(gè)新的套接字,發(fā)送和接收都是用這個(gè)套接字 */

if (-1 == (ClientFd = accept(ServerFd, (struct sockaddr*)&ClientAddr, &addr_len)))

{

printf("accept error! ");

exit(1);

}

/* 接受客戶端的返回?cái)?shù)據(jù) */

if ((recv_len = recv(ClientFd, Buf, BUF_LEN, 0)) < 0)

{

printf("recv error! ");

exit(1);

}

printf("客戶端發(fā)送過來(lái)的數(shù)據(jù)為:%s ", Buf);

/* 發(fā)送數(shù)據(jù)到客戶端 */

send(ClientFd, Buf, recv_len, 0);

/* 關(guān)閉客戶端套接字 */

close(ClientFd);

/* 清空緩沖區(qū) */

memset(Buf, 0, BUF_LEN);

}

return 0;

}

客戶端程序linux_tcp_client.c:

#include

#include

#include

#include

#include

#include

#define BUF_LEN 100

int main(void)

{

int ClientFd;

char Buf[BUF_LEN] = {0};

struct sockaddr_in ServerSockAddr;

/* 向服務(wù)器發(fā)起請(qǐng)求 */

memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));

ServerSockAddr.sin_family = AF_INET;

ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

ServerSockAddr.sin_port = htons(6666);

while (1)

{

/* 創(chuàng)建客戶端socket */

if (-1 == (ClientFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))

{

printf("socket error! ");

exit(1);

}

/* 連接 */

if (-1 == connect(ClientFd, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr)))

{

printf("connect error! ");

exit(1);

}

printf("請(qǐng)輸入一個(gè)字符串,發(fā)送給服務(wù)端:");

gets(Buf);

/* 發(fā)送數(shù)據(jù)到服務(wù)端 */

send(ClientFd, Buf, strlen(Buf), 0);

memset(Buf, 0, BUF_LEN); // 重置緩沖區(qū)

/* 接受服務(wù)端的返回?cái)?shù)據(jù) */

recv(ClientFd, Buf, BUF_LEN, 0);

printf("服務(wù)端發(fā)送過來(lái)的數(shù)據(jù)為:%s ", Buf);

memset(Buf, 0, BUF_LEN); // 重置緩沖區(qū)

close(ClientFd); // 關(guān)閉套接字

}

return 0;

}

Linux下編譯就不需要添加-lwsock32參數(shù):

gcc linux_tcp_server.c -o linux_tcp_server

gcc linux_tcp_client.c -o linux_tcp_client

實(shí)驗(yàn)現(xiàn)象:

在調(diào)試這份程序時(shí),出現(xiàn)了綁定錯(cuò)誤:

經(jīng)上網(wǎng)查詢發(fā)現(xiàn)是端口重復(fù)使用,可以在調(diào)用bind()函數(shù)之前調(diào)用setsockopt()函數(shù)以解決端口重復(fù)使用的問題:

2、基于UDP的本地客戶端、服務(wù)端信息交互實(shí)例

(1)windows的程序

服務(wù)端程序udp_server.c:

#include

#include

#define BUF_LEN 100

int main(void)

{

WSADATA wd;

SOCKET ServerSock;

char Buf[BUF_LEN] = {0};

SOCKADDR ClientAddr;

SOCKADDR_IN ServerSockAddr;

int addr_size = 0;

/* 初始化操作sock需要的DLL */

WSAStartup(MAKEWORD(2,2),&wd);

/* 創(chuàng)建服務(wù)端socket */

if(-1 == (ServerSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))

{

printf("socket error! ");

exit(1);

}

/* 設(shè)置服務(wù)端信息 */

memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 給結(jié)構(gòu)體ServerSockAddr清零

ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址

ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 自動(dòng)獲取IP地址

ServerSockAddr.sin_port = htons(1314); // 端口

/* 綁定套接字 */

if (-1 == (bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR))))

{

printf("bind error! ");

exit(1);

}

addr_size = sizeof(SOCKADDR);

while (1)

{

/* 接受客戶端的返回?cái)?shù)據(jù) */

int str_len = recvfrom(ServerSock, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);

printf("客戶端發(fā)送過來(lái)的數(shù)據(jù)為:%s ", Buf);

/* 發(fā)送數(shù)據(jù)到客戶端 */

sendto(ServerSock, Buf, str_len, 0, &ClientAddr, addr_size);

/* 清空緩沖區(qū) */

memset(Buf, 0, BUF_LEN);

}

/*如果有退出循環(huán)的條件,這里還需要清除對(duì)socket庫(kù)的使用*/

/* 關(guān)閉服務(wù)端套接字 */

//closesocket(ServerSock);

/* WSACleanup();*/

return 0;

}

客戶端程序udp_client.c:

#include

#include

#define BUF_LEN 100

int main(void)

{

WSADATA wd;

SOCKET ClientSock;

char Buf[BUF_LEN] = {0};

SOCKADDR ServerAddr;

SOCKADDR_IN ServerSockAddr;

int ServerAddrLen = 0;

/* 初始化操作sock需要的DLL */

WSAStartup(MAKEWORD(2,2),&wd);

/* 創(chuàng)建客戶端socket */

if (-1 == (ClientSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))

{

printf("socket error! ");

exit(1);

}

/* 向服務(wù)器發(fā)起請(qǐng)求 */

memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));

ServerSockAddr.sin_family = PF_INET;

ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

ServerSockAddr.sin_port = htons(1314);

ServerAddrLen = sizeof(ServerAddr);

while (1)

{

printf("請(qǐng)輸入一個(gè)字符串,發(fā)送給服務(wù)端:");

gets(Buf);

/* 發(fā)送數(shù)據(jù)到服務(wù)端 */

sendto(ClientSock, Buf, strlen(Buf), 0, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr));

/* 接受服務(wù)端的返回?cái)?shù)據(jù) */

recvfrom(ClientSock, Buf, BUF_LEN, 0, &ServerAddr, &ServerAddrLen);

printf("服務(wù)端發(fā)送過來(lái)的數(shù)據(jù)為:%s ", Buf);

memset(Buf, 0, BUF_LEN); // 重置緩沖區(qū)

}

closesocket(ClientSock); // 關(guān)閉套接字

// WSACleanup(); /*如果有退出循環(huán)的條件,這里還需要清除對(duì)socket庫(kù)的使用*/

return 0;

}

(2)Linux下的程序

服務(wù)端程序linux_udp_server.c:

#include

#include

#include

#include

#include

#include

#include

#define BUF_LEN 100

int main(void)

{

int ServerFd;

char Buf[BUF_LEN] = {0};

struct sockaddr ClientAddr;

struct sockaddr_in ServerSockAddr;

int addr_size = 0;

int optval = 1;

/* 創(chuàng)建服務(wù)端socket */

if ( -1 == (ServerFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))

{

printf("socket error! ");

exit(1);

}

/* 設(shè)置服務(wù)端信息 */

memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 給結(jié)構(gòu)體ServerSockAddr清零

ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址

ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 自動(dòng)獲取IP地址

ServerSockAddr.sin_port = htons(1314); // 端口

// 設(shè)置地址和端口號(hào)可以重復(fù)使用

if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)

{

printf("setsockopt error! ");

exit(1);

}

/* 綁定操作,綁定前加上上面的socket屬性可重復(fù)使用地址 */

if (-1 == bind(ServerFd, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr)))

{

printf("bind error! ");

exit(1);

}

addr_size = sizeof(ClientAddr);

while (1)

{

/* 接受客戶端的返回?cái)?shù)據(jù) */

int str_len = recvfrom(ServerFd, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);

printf("客戶端發(fā)送過來(lái)的數(shù)據(jù)為:%s ", Buf);

/* 發(fā)送數(shù)據(jù)到客戶端 */

sendto(ServerFd, Buf, str_len, 0, &ClientAddr, addr_size);

/* 清空緩沖區(qū) */

memset(Buf, 0, BUF_LEN);

}

close(ServerFd);

return 0;

}

客戶端程序linux_udp_client.c:

#include

#include

#include

#include

#include

#include

#define BUF_LEN 100

int main(void)

{

int ClientFd;

char Buf[BUF_LEN] = {0};

struct sockaddr ServerAddr;

int addr_size = 0;

struct sockaddr_in ServerSockAddr;

/* 創(chuàng)建客戶端socket */

if (-1 == (ClientFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))

{

printf("socket error! ");

exit(1);

}

/* 向服務(wù)器發(fā)起請(qǐng)求 */

memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));

ServerSockAddr.sin_family = PF_INET;

ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

ServerSockAddr.sin_port = htons(1314);

addr_size = sizeof(ServerAddr);

while (1)

{

printf("請(qǐng)輸入一個(gè)字符串,發(fā)送給服務(wù)端:");

gets(Buf);

/* 發(fā)送數(shù)據(jù)到服務(wù)端 */

sendto(ClientFd, Buf, strlen(Buf), 0, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr));

/* 接受服務(wù)端的返回?cái)?shù)據(jù) */

recvfrom(ClientFd, Buf, BUF_LEN, 0, &ServerAddr, &addr_size);

printf("服務(wù)端發(fā)送過來(lái)的數(shù)據(jù)為:%s ", Buf);

memset(Buf, 0, BUF_LEN); // 重置緩沖區(qū)

}

close(ClientFd); // 關(guān)閉套接字

return 0;

}

實(shí)驗(yàn)現(xiàn)象:

實(shí)驗(yàn)現(xiàn)象如實(shí)例1。

五、總結(jié)

本筆記簡(jiǎn)單介紹了一些與socket編程相關(guān)的一些知識(shí)點(diǎn):IP地址,什么是端口,協(xié)議等。重點(diǎn)介紹了TCP、UDP通信的一些原理及其API接口的用法,并給出了windows和linux下的TCP、UDP通信實(shí)例。以上就是關(guān)于socket編程的一些總結(jié),如有錯(cuò)誤,歡迎指出!

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 嵌入式
    +關(guān)注

    關(guān)注

    5152

    文章

    19676

    瀏覽量

    317696
  • 通信
    +關(guān)注

    關(guān)注

    18

    文章

    6206

    瀏覽量

    137843
  • Socket
    +關(guān)注

    關(guān)注

    1

    文章

    212

    瀏覽量

    35877

原文標(biāo)題:【socket筆記】TCP、UDP通信總結(jié)

文章出處:【微信號(hào):mcu168,微信公眾號(hào):硬件攻城獅】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    socket 和 WebSocket 的區(qū)別

    在現(xiàn)代網(wǎng)絡(luò)通信中,Socket和WebSocket是種常見的通信協(xié)議。它們?cè)?b class='flag-5'>實(shí)現(xiàn)網(wǎng)絡(luò)通信、數(shù)據(jù)
    的頭像 發(fā)表于 11-12 14:33 ?1482次閱讀

    socket 客戶與服務(wù)器的實(shí)現(xiàn)

    1. 理解Socket 在計(jì)算機(jī)網(wǎng)絡(luò)中,socket是一種通信機(jī)制,允許個(gè)程序(一個(gè)客戶和一個(gè)服務(wù)器)通過網(wǎng)絡(luò)進(jìn)行
    的頭像 發(fā)表于 11-12 14:30 ?824次閱讀

    socket 與 RESTful API 的使用

    。 1.2 Socket的特點(diǎn) 雙向通信Socket支持客戶和服務(wù)器之間的雙向通信,可以
    的頭像 發(fā)表于 11-12 14:22 ?943次閱讀

    socket 加密通信實(shí)現(xiàn)方式

    在網(wǎng)絡(luò)通信中,數(shù)據(jù)的安全性至關(guān)重要。Socket 編程作為網(wǎng)絡(luò)通信的基礎(chǔ),實(shí)現(xiàn)加密通信是保護(hù)數(shù)據(jù)
    的頭像 發(fā)表于 11-12 14:18 ?1465次閱讀

    socket 多線程編程實(shí)現(xiàn)方法

    在現(xiàn)代網(wǎng)絡(luò)編程中,多線程技術(shù)被廣泛應(yīng)用于提高服務(wù)器的并發(fā)處理能力。Socket編程是網(wǎng)絡(luò)通信的基礎(chǔ),而將多線程技術(shù)應(yīng)用于Socket
    的頭像 發(fā)表于 11-12 14:16 ?1020次閱讀

    socket 網(wǎng)絡(luò)通信協(xié)議解析

    通信機(jī)制,它允許個(gè)程序(通常位于不同的計(jì)算機(jī)上)進(jìn)行雙向通信。在網(wǎng)絡(luò)編程中,Socket被用來(lái)實(shí)現(xiàn)
    的頭像 發(fā)表于 11-12 14:04 ?5276次閱讀

    socket 編程基礎(chǔ)入門

    Socket 編程基礎(chǔ)入門 在計(jì)算機(jī)網(wǎng)絡(luò)中,Socket 是一個(gè)抽象層,它將網(wǎng)絡(luò)通信的細(xì)節(jié)隱藏起來(lái),為開發(fā)者提供了一個(gè)簡(jiǎn)單的接口來(lái)發(fā)送和接收數(shù)據(jù)。S
    的頭像 發(fā)表于 11-12 14:03 ?1042次閱讀

    socket與WebSocket的區(qū)別與聯(lián)系

    在現(xiàn)代網(wǎng)絡(luò)通信中,Socket和WebSocket是種常見的通信協(xié)議。它們?cè)诰W(wǎng)絡(luò)編程中扮演著重要的角色,但它們
    的頭像 發(fā)表于 11-04 09:19 ?1261次閱讀

    C語(yǔ)言中的socket編程基礎(chǔ)

    Socket編程簡(jiǎn)介 Socket是一種通信機(jī)制,允許程序之間進(jìn)行通信。在C語(yǔ)言中,
    的頭像 發(fā)表于 11-01 16:51 ?1242次閱讀

    socket編程中的阻塞與非阻塞

    在網(wǎng)絡(luò)編程中, socket 是一個(gè)非常重要的概念,它提供了一個(gè)抽象層,使得開發(fā)者可以不必關(guān)心底層的網(wǎng)絡(luò)通信細(xì)節(jié)。 socket 編程中的阻
    的頭像 發(fā)表于 11-01 16:13 ?754次閱讀

    什么是socket編程 socket與tcp/ip協(xié)議的關(guān)系

    什么是Socket編程 Socket編程是一種網(wǎng)絡(luò)編程技術(shù),它允許程序之間進(jìn)行
    的頭像 發(fā)表于 11-01 16:01 ?1211次閱讀

    瞬時(shí)極性法電容兩端極性判斷

    瞬時(shí)極性法電容兩端極性判斷是電子電路中的一個(gè)重要問題,涉及到電容的工作原理、特性以及在電路中的應(yīng)用。 一、電容的基本概念 電容的定義 電容是一種電子元件,能夠存儲(chǔ)電荷。它的單位是法拉(F),通常用微
    的頭像 發(fā)表于 08-21 16:01 ?2681次閱讀

    socket和http區(qū)別是什么

    是操作系統(tǒng)提供的網(wǎng)絡(luò)編程接口,它允許程序員編寫能夠進(jìn)行網(wǎng)絡(luò)通信的程序。 HTTP(超文本傳輸協(xié)議)是一種用于分布式、協(xié)作式和超媒體信息系統(tǒng)的應(yīng)用層協(xié)議。它是Web的基礎(chǔ),用于在客戶和服務(wù)器
    的頭像 發(fā)表于 08-16 11:04 ?1574次閱讀

    簡(jiǎn)述socket編程中的常用函數(shù)

    Socket編程是一種基于TCP/IP協(xié)議的網(wǎng)絡(luò)編程技術(shù),它允許應(yīng)用程序通過網(wǎng)絡(luò)進(jìn)行通信。在Socket
    的頭像 發(fā)表于 08-16 10:49 ?892次閱讀

    如何理解socket編程接口

    不同計(jì)算機(jī)之間進(jìn)行通信的方法。在網(wǎng)絡(luò)編程中,Socket被廣泛用于實(shí)現(xiàn)客戶和服務(wù)器
    的頭像 發(fā)表于 08-16 10:48 ?929次閱讀