學習LInux,網路程式設計套接字是基礎,也是新手學習的困難點,透過這篇文章,讀者可以透過圖解、作者的程式碼實現思路全面理解IP位址、連接埠號碼、TCP、UDP概念、socket API用法、模擬客戶端/伺服器通訊等。
IP位址、連接埠號碼…
#socket API
實作UDP客戶端/伺服器
前面介紹過,本地的進程間通訊(IPC)有很多種方式,常見的總結以下幾點:
1、管道(包括无名管道和命名管道); 2、消息队列; 3、信号量; 4、共享存储。 5、……( Socket和Streams支持不同主机上的两个进程IPC)。
(IP就是:Internet協定IP)
在通訊時,IP有源IP和目的IP之分,
對比寄快遞:網路通訊相當於收發快遞,IP就是收件/寄件者地址,光知道地址還不行,還要知道派送人是誰?這就比較於網路中的連接埠號碼概念,連接埠號碼標識了一個進程,告訴作業系統,目前這個資料交給哪一個程式來解析。
連接埠號碼(port)是傳輸層協定的內容。
連接埠號碼是一個2位元組16位元的整數;
連接埠號碼用來識別一個進程,告訴作業系統,目前這個資料交給哪一個程式解析;
IP位址連接埠號碼能標識網路上的某一主機的某一進程;
一個連接埠號碼只能被一個行程佔用。
進程有唯一的pid標識,連接埠號碼也能標識進程;
一個進程可以綁定多個連接埠號,一個連接埠號碼不能被多個進程綁定。
#傳輸層協定(TCP/IP)的資料段中有兩個埠號,分別叫做來源埠號和目的埠號,就是在描述「資料是誰的?寄給誰?」
(TCP)傳輸控制協議,面向連線。是一種提供可靠資料傳輸的通用協定。
傳輸層協定
有連接
#可靠地傳輸
#面向位元組流
(UDP)使用者資料報協議,是一個面向無連線的協定。採用該協定不需要兩個應用程式先建立連線。 UDP協定不提供錯誤恢復,無法提供資料重傳,因此該協定傳輸資料安全性差。
傳輸層協定
無連線
不可靠地傳輸
#面向資料封包
#如何定義網路資料流的位址?
//创建socket文件描述符 (TCP/UDP,客户端+服务器) int socket(int domain, int type, int protocol);
参数1(domain): 选择创建的套接字所用的协议族; <br/> AF_INET : IPv4协议; <br/> AF_INET6: IPv6协议; <br/> AF_LOCAL: Unix域协议; <br/> AF_ROUTE:路由套接口; <br/> AF_KEY :密钥套接口。 <br/>参数2(type):指定套接口类型,所选类型有: <br/> SOCK_STREAM:字节流套接字; <br/> SOCK_DGRAM : 数据报套接字; <br/> SOCK_RAW : 原始套接口。 <br/> procotol: 使用的特定协议,一般使用默认协议(NULL)。
//绑定端口号 (TCP/IP,服务器) int bind(int socket, const struct sockaddr *address, socklen_t address_len);
参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。 <br/>参数2(address):指向特定协议的地址指针。 <br/>参数3(address_len):上面地址结构的长度。 <br/>返回值:没有错误,bind()返回0,否则SOCKET_ERROR。
//开始监听socket (TCP,服务器) int listen(int socket, int backlog);
参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。 <br/>参数2(backlog):所监听的端口队列大小。
//接受请求 (TCP,服务器) int accept(int socket, struct sockaddr* address, socklen_t* address_len);
参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。 <br/>参数2(address):指向特定协议的地址指针。 <br/>参数3(addrlen):上面地址结构的长度。 <br/>返回值:没有错误,bind()返回0,否则SOCKET_ERROR。
//建立连接 (TCP,客户端) int connect(int sockfd, const struct struct sockaddr *addr, aocklen_t addrlen);
//关闭套接字 int close(int fd);
参数(fd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4,IPv6,……
服务器代码:
#include<iostream> #include<unistd.h> #include<stdio.h> #include<string.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> using namespace std; #define SERVER_PORT 5050 //端口号 #define SERVER_IP "192.168.3.254" //服务器ip #define QUEUE_SIZE 5 //所监听端口队列大小 int main(int argc, char *argv[]) { //创建一个套接字,并检测是否创建成功 int sockSer; sockSer = socket(AF_INET, SOCK_STREAM, 0); if(sockSer == -1){ perror("socket"); } //设置端口可以重用,可以多个客户端连接同一个端口,并检测是否设置成功 int yes = 1; if(setsockopt(sockSer, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){ perror("setsockopt"); } struct sockaddr_in addrSer,addrCli; //创建一个记录地址信息的结构体 addrSer.sin_family = AF_INET; //所使用AF_INET协议族 addrSer.sin_port = htons(SERVER_PORT); //设置地址结构体中的端口号 addrSer.sin_addr.s_addr = inet_addr(SERVER_IP); //设置其中的服务器ip //将套接字地址与所创建的套接字号联系起来。并检测是否绑定成功 socklen_t addrlen = sizeof(struct sockaddr); int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen); if(res == -1) perror("bind"); listen(sockSer, QUEUE_SIZE); //监听端口队列是否由连接请求,如果有就将该端口设置位可连接状态,等待服务器接收连接 printf("Server Wait Client Accept......\n"); //如果监听到有连接请求接受连接请求。并检测是否连接成功,成功返回0,否则返回-1 int sockConn = accept(sockSer, (struct sockaddr*)&addrCli, &addrlen); if(sockConn == -1) perror("accept"); else { printf("Server Accept Client OK.\n"); printf("Client IP:> %s\n", inet_ntoa(addrCli.sin_addr)); printf("Client Port:> %d\n",ntohs(addrCli.sin_port)); } char sendbuf[256]; //申请一个发送缓存区 char recvbuf[256]; //申请一个接收缓存区 while(1) { printf("Ser:>"); scanf("%s",sendbuf); if(strncmp(sendbuf,"quit",4) == 0) //如果所要发送的数据为"quit",则直接退出。 break; send(sockConn, sendbuf, strlen(sendbuf)+1, 0); //发送数据 recv(sockConn, recvbuf, 256, 0); //接收客户端发送的数据 printf("Cli:> %s\n",recvbuf); } close(sockSer); //关闭套接字 return 0; }
客户端代码:
#include<iostream> #include<unistd.h> #include<stdio.h> #include<string.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> using namespace std; #define SERVER_PORT 5050 #define SERVER_IP "192.168.3.254" int main(int argc, char *argv[]) { //创建客户端套接字号,并检测是否创建成功 int sockCli; sockCli = socket(AF_INET, SOCK_STREAM, 0); if(sockCli == -1) perror("socket"); //创建一个地址信息结构体,并对其内容进行设置 struct sockaddr_in addrSer; addrSer.sin_family = AF_INET; //使用AF_INET协议族 addrSer.sin_port = htons(SERVER_PORT); //设置端口号 addrSer.sin_addr.s_addr = inet_addr(SERVER_IP); //设置服务器ip bind(sockCli,(struct sockaddr*)&addrCli, sizeof(struct sockaddr)); //将套接字地址与所创建的套接字号联系起来 //创建一个与服务器的连接,并检测连接是否成功 socklen_t addrlen = sizeof(struct sockaddr); int res = connect(sockCli,(struct sockaddr*)&addrSer, addrlen); if(res == -1) perror("connect"); else printf("Client Connect Server OK.\n"); char sendbuf[256]; //申请一个发送数据缓存区 char recvbuf[256]; //申请一个接收数据缓存区 while(1) { recv(sockCli, recvbuf, 256, 0); //接收来自服务器的数据 printf("Ser:> %s\n",recvbuf); printf("Cli:>"); scanf("%s",sendbuf); if(strncmp(sendbuf,"quit", 4) == 0) //如果客户端发送的数据为"quit",则退出。 break; send(sockCli, sendbuf, strlen(sendbuf)+1, 0); //发送数据 } close(sockCli); //关闭套接字 return 0; }
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号) <br/>参数2(buf):指向存有发送数据的缓冲区的指针 <br/>参数3(len):缓冲区长度。 <br/> **参数4(flags):**flags的值或为0,或为其他
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号) <br/>参数2(buf):指向存有接收数据的缓冲区的指针 <br/>参数3(len):缓冲区长度 <br/> **参数4(flags):**flags的值或为0,或为其他
服务器端代码:
#include<stdio.h> #include<unistd.h> #include<string.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/socket.h> int main() { //创建一个套接字,并检测是否创建成功 int sockSer = socket(AF_INET, SOCK_DGRAM, 0); if(sockSer == -1) perror("socket"); struct sockaddr_in addrSer; //创建一个记录地址信息的结构体 addrSer.sin_family = AF_INET; //使用AF_INET协议族 addrSer.sin_port = htons(5050); //设置地址结构体中的端口号 addrSer.sin_addr.s_addr = inet_addr("192.168.3.169"); //设置通信ip //将套接字地址与所创建的套接字号联系起来,并检测是否绑定成功 socklen_t addrlen = sizeof(struct sockaddr); int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen); if(res == -1) perror("bind"); char sendbuf[256]; //申请一个发送数据缓存区 char recvbuf[256]; //申请一个接收数据缓存区 struct sockaddr_in addrCli; while(1) { recvfrom(sockSer,recvbuf,256,0,(struct sockaddr*)&addrCli, &addrlen); //从指定地址接收客户端数据 printf("Cli:>%s\n",recvbuf); printf("Ser:>"); scanf("%s",sendbuf); sendto(sockSer,sendbuf,strlen(sendbuf)+1,0,(struct sockaddr*)&addrCli, addrlen); //向客户端发送数据 } return 0; }
客户端代码:
#include<stdio.h> #include<unistd.h> #include<string.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/socket.h> int main() { //创建一个套接字,并检测是否创建成功 int sockCli = socket(AF_INET, SOCK_DGRAM, 0); if(sockCli == -1){ perror("socket"); } addrSer.sin_family = AF_INET; //使用AF_INET协议族 addrSer.sin_port = htons(5050); //设置地址结构体中的端口号 addrSer.sin_addr.s_addr = inet_addr("192.168.3.169"); //设置通信ip socklen_t addrlen = sizeof(struct sockaddr); char sendbuf[256]; //申请一个发送数据缓存区 char recvbuf[256]; //申请一个接收数据缓存区 while(1){ //向客户端发送数据 printf("Cli:>"); scanf("%s",sendbuf); sendto(sockCli, sendbuf, strlen(sendbuf)+1, 0, (struct sockaddr*)&addrSer, addrlen); 接收来自客户端的数据 recvfrom(sockCli, recvbuf, BUFFER_SIZE, 0, (struct sockaddr*)&addrSer, &addrlen); printf("Ser:>%s\n", recvbuf); } return 0; }
<br/>
以上是socket套接字詳解(TCP與UDP)的詳細內容。更多資訊請關注PHP中文網其他相關文章!