嵌入式Linux網路程式設計,網路基礎,TCP程式設計,socket(),bind(),listen(),accept(),connect(),send()/recv(),close()/shutdown()
文章目錄
- 1,建立socket檔案描述符socket()
- 2, 繫結bind()
- 3,把主動套接字變成被動套接字listen()
- 4,阻塞等待客戶端連線請求accept()
- 5,客戶端的連線函式connect()
- 6,傳送資料send()、write()
- 7,接受資料 recv()、read()
- 8,套接字的關閉 close()、shutdown()
- 9,示例
1,建立socket檔案描述符socket()
int socket (int domain, int type, int protocol);
- domain 是地址族(域名)
domain | 含義 |
---|---|
PF_NS | // Xerox NS協議 |
PF_IMPLINK | // Interface Message協議 |
AF_INET | IPv4 Internet protocols ip(7) // internet 協議 |
AF_INET6 | IPv6 Internet protocols ipv6(7) |
AF_UNIX,AF_LOCAL | Local communication unix(7)// unix internal協議 |
AF_NETLINK | Kernel user interface device netlink(7) |
AF_PACKET | Low level packet interface packet(7) |
- type // 套接字型別
·SOCK_STREAM // 流式套接字,唯一對應於TCP
·SOCK_DGRAM // 資料報套接字,唯一對應著UDP
·SOCK_RAW // 原始套接字 - protocol 引數通常置為0,原始套接字程式設計時需填充
- 返回值
·On success, a file descriptor for the new socket is returned.
·On error, -1 is returned, and errno is set appropriately.
·成功時返回檔案描述符,出錯時返回為-1
2, 繫結bind()
int bind (int sockfd, struct sockaddr* addr, int addrLen);
-
sockfd 由socket() 呼叫返回
-
addr 是指向 sockaddr_in 結構的指標,包含本機IP 地址和埠號
struct sockaddr_inu_short sin_family // protocol family u_short sin_port // port number struct in_addr sin_addr //IP address (32-bits)
-
addrLen(地址長度) : sizeof (struct sockaddr_in)
-
返回值
·On success, zero is returned.
·On error, -1 is returned, and errno is set appropriately.
2.1, 地址相關的資料結構struct sockaddr、struct sockaddr_in、struct in_addr
- 通用地址結構
struct sockaddr
{
u_short sa_family; //2位元組, 地址族, AF_xxx
char sa_data[14]; // 14位元組協議地址
};
- Internet協議地址結構
struct sockaddr_in
{
u_short sin_family; // 地址族, AF_INET,2 bytes
u_short sin_port; // 埠,2 bytes
struct in_addr sin_addr; // IPV4地址,4 bytes
char sin_zero[8]; // 8 bytes unused,作為填充必須清零
};
- IPv4地址結構
// internet address
struct in_addr
{
in_addr_t s_addr; // u32 network address
};
3,把主動套接字變成被動套接字listen()
int listen (int sockfd, int backlog);
- sockfd:監聽連線的套接字,通過socket()函式拿到的fd
- backlog
·指定了正在等待連線的最大佇列長度,它的作用在於處理可能同時出現的幾個連線請求。
·同時允許幾路客戶端和伺服器進行正在連線的過程(正在三次握手)一般填5, 測試得知,ARM最大為8
·DoS(拒絕服務)攻擊即利用了這個原理,非法的連線佔用了全部的連線數,造成正常的連線請求被拒絕。
- 核心中伺服器的套接字fd會維護2個連結串列:
1. 正在三次握手的的客戶端連結串列(數量=2*backlog+1)
2. 已經建立好連線的客戶端連結串列(已經完成3次握手分配好了newfd)
- 返回值: 成功返回0 或 失敗返回-1
完成listen()呼叫後,socket變成了監聽socket(listening socket).
4,阻塞等待客戶端連線請求accept()
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) ;
- sockfd : 監聽套接字 ,經過前面socket()建立並通過bind(),listen()設定過的fd
- addr : 對方地址1(核心自動取到連線過來的客戶端的資訊)
- addrlen:地址長度
- 返回值:
·On success, these system calls return a nonnegative integer that is a descriptor for the accepted socket.
·On error, -1 is returned, and errno is set appropriately.
·成功時返回已經建立好連線的新的newfd
listen()和accept()是TCP伺服器端使用的函式
5,客戶端的連線函式connect()
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
使用方法和伺服器的bind()函式類似
- sockfd : socket返回的檔案描述符
- serv_addr : 伺服器端的地址資訊
- addrlen : serv_addr的長度
- 返回值:0 或 -1
connect()是客戶端使用的系統呼叫。
6,傳送資料send()、write()
send() | write() |
---|---|
ssize_t send(int socket, const void *buffer, size_t length, int flags); | ssize_t write(int fd, const void *buf, size_t count); |
#include <sys/socket.h>
ssize_t send(int socket, const void *buffer, size_t length, int flags);
- buffer : 傳送緩衝區首地址
- length : 傳送的位元組數
- flags : 傳送方式(通常為0,填0的時候和write()一樣)
flags | 含義 |
---|---|
MSG_DONTWAIT | Enables nonblocking operation;非阻塞傳送 |
MSG_OOB | 用以傳送TCP型別的帶外資料(out-of-band) |
- 返回值:
·成功:實際傳送的位元組數
·失敗:-1, 並設定errno
7,接受資料 recv()、read()
recv() | read() |
---|---|
ssize_t recv(int socket, const void *buffer, size_t length, int flags); | ssize_t read(int fd, void *buf, size_t count); |
#include <sys/socket.h>
ssize_t recv(int socket, const void *buffer, size_t length, int flags);
- buffer : 傳送緩衝區首地址
- length : 傳送的位元組數
- flags : 接收方式(通常為0,填0的時候和read()一樣)
flags | 含義 |
---|---|
MSG_DONTWAIT | Enables nonblocking operation;非阻塞傳送 |
MSG_OOB | 用以傳送TCP型別的帶外資料(out-of-band) |
MSG_PEEK | This flag causes the receive operation to return data from thebeginning of the receive queue without removing that data from the queue. Thus, a subsequent receive call will return the same data.核心會從網路接受資料,並填充緩衝區,read()函式讀掉 的資料在緩衝區中就沒了;recv()函式用了此引數,讀完之後,資料任然存在 |
- 返回值:
·成功:實際接收的位元組數
·失敗:-1, 並設定errno
8,套接字的關閉 close()、shutdown()
8.1,關閉雙向通訊 close()
int close(int sockfd);
8.2,選擇關閉 shutdown()
int shutdown(int sockfd, int howto);
- TCP連線是雙向的(是可讀寫的),當我們使用close時,會把讀寫通道都關閉,有時侯我們希望只關閉一個方向,這個時候我們可以使用shutdown。
- howto
howto | 含義 |
---|---|
SHUT_RD | further receptions will be disallowed關閉讀通道,但是可以繼續往套接字寫資料 |
SHUT_WR | further transmissions will be disallowed關閉寫通道。只能從套接字讀取資料 |
SHUT_RDWR | further recep‐tions and transmissions will be disallowed,和close()效果一樣 |
- RETURN VALUE
·On success, zero is returned.
·On error, -1 is returned, and errno is set appropriately.
9,示例
9.1,標頭檔案<net.h>
#ifndef __NET_H__
#define __NET_H__
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.199.200"
#define BACKLOG 5
#define QUIT_STR "quit"
#endif
9.2,伺服器端程式碼<service.c>
#include "net.h"
int main(void)
{
int fd = -1;
struct sockaddr_in sin; //如果是IPV6的程式設計,要使用struct sockddr_in6結構體(詳細情況請參考man 7 ipv6),通常更通用的方法可以通過struct sockaddr_storage來程式設計
/* 1 建立socket fd */
if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0){ //AF_INET IPV4程式設計
perror("sockket");
exit(1);
}
/* 2 繫結 */
/* 2.1 填充struct sockaddr_in結構體變數 */
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);//網路位元組序的埠號
/* 優化1 讓伺服器可以繫結在任意的IP上 */
#if 1
sin.sin_addr.s_addr = htonl(INADDR_ANY);// 優化1 讓伺服器可以繫結在任意的IP上
#else
if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)sin.sin_addr.s_addr) < 0){ //sin.sin_addr.s_addr等價於sin.sin_addr
//AF_INET IPV4程式設計
perror("inet_pton");
exit(1);
}
#endif
/* 2.2 繫結 */
if(bind(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0){
perror("bind");
exit(1);
}
/* 3 呼叫listen() 把主動套接字變成被動套接字 */
if(listen(fd,BACKLOG) <0){
perror("listen");
exit(1);
}
int newfd = -1;
/* 4 阻塞等待客戶端連線請求 */
#if 0
if((newfd = accept(fd,NULL,NULL)) < 0)//不關心客戶端資訊,來了就為其服務
{
perror("accept");
exit(1);
}
/* 優化2 通過程式獲取剛建立連線的socket的客戶端的IP地址和埠號 */
#else
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
if((newfd = accept(fd,(struct sockaddr *)&cin,&addrlen)) < 0)
{
perror("accept");
exit(1);
}
char ipv4_addr[16];
if(! inet_ntop(AF_INET,(void *)&cin.sin_addr.s_addr,ipv4_addr,sizeof(cin)))//將網路位元組序形式的IP地址轉化為本地點分形式的字串IP地址
{
perror("inet_ntop");
exit(1);
}
printf("Client (:%s is connected port:%d\n",ipv4_addr,ntohs(cin.sin_port));
#endif
/* 5 讀寫 */
int ret = -1;//read()是個阻塞函式,要做讀寫錯誤的工程處理
char buf[BUFSIZ];//BUFSIZE是系統提供的
while(1)
{
bzero(buf,BUFSIZ);//首先將buf清零
do{
ret = read(newfd,buf,BUFSIZ-1);//防止陣列下標越界BUFSIZE-1
}while(ret < 0 && EINTR == errno);
if(ret < 0)
{
perror("read");
exit(1);
}
if(!ret){ //對方已經關閉
break;
}
printf("receive data: %s",buf);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){ //使用者輸入了quit字元
printf("Client is existing!\n");
break;
}
}
close(newfd);
close(fd);
return 0;
}
9.3,客戶端程式碼<client.c>
#include "net.h"
int main(void)
{
int fd = -1;
struct sockaddr_in sin; //如果是IPV6的程式設計,要使用struct sockddr_in6結構體(詳細情況請參考man 7 ipv6),通常更通用的方法可以通過struct sockaddr_storage來程式設計
/* 1 建立socket fd */
if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0){ //AF_INET IPV4程式設計
perror("sockket");
exit(1);
}
/* 2 連線伺服器 */
/* 2.1 填充struct sockaddr_in結構體變數 */
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);//網路位元組序的埠號
#if 1
if((sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR)) < 0){
perror("inet_addr");
exit(1);
}
#else
if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)sin.sin_addr.s_addr) < 0){ //sin.sin_addr.s_addr等價於sin.sin_addr
//AF_INET IPV4程式設計
perror("inet_pton");
exit(1);
}
#endif
/* 2.2 連線伺服器 */
if(connect(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0){
perror("connect");
exit(1);
}
/* 3 讀寫資料 */
char buf[BUFSIZ];//BUFSIZE是系統提供的
while(1)
{
bzero(buf,BUFSIZ);//首先將buf清零
if(fgets(buf,BUFSIZ-1,stdin) == NULL)//放置陣列下標越界BUFSIZE-1
{
continue;
}
write(fd,buf,strlen(buf));
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){ //使用者輸入了quit字元
printf("Client is existing!\n");
break;
}
}
/* 4 關閉套接字 */
close(fd);
return 0;
}
9.4,執行客戶端
[email protected]:~/test/network$ ./client
asda
axsa
as
quite
Client is existing!
9.5,執行伺服器端
[email protected]:~/test/network$ ./service
Client (:192.168.199.200 is connected port:44644
receive data: asda
receive data: axsa
receive data: as
receive data: quite
Client is existing!