1. 程式人生 > >嵌入式Linux網路程式設計,網路基礎,TCP程式設計,socket(),bind(),listen(),accept(),connect(),send()/recv(),close()/shutdown()

嵌入式Linux網路程式設計,網路基礎,TCP程式設計,socket(),bind(),listen(),accept(),connect(),send()/recv(),close()/shutdown()

文章目錄

1,建立socket檔案描述符socket()

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

  1. 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)
  1. type // 套接字型別
    ·SOCK_STREAM // 流式套接字,唯一對應於TCP
    ·SOCK_DGRAM // 資料報套接字,唯一對應著UDP
    ·SOCK_RAW // 原始套接字
  2. protocol 引數通常置為0,原始套接字程式設計時需填充
  3. 返回值
    ·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);

  1. sockfd 由socket() 呼叫返回

  2. addr 是指向 sockaddr_in 結構的指標,包含本機IP 地址和埠號
    struct sockaddr_in

    u_short sin_family // protocol family		
    u_short sin_port     // port number
    struct in_addr  sin_addr  //IP address (32-bits)
    
  3. addrLen(地址長度) : sizeof (struct sockaddr_in)

  4. 返回值
    ·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

  1. 通用地址結構
  struct sockaddr
  {    
       u_short  sa_family;    //2位元組, 地址族, AF_xxx
       char  sa_data[14];     // 14位元組協議地址
  };
  1. 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,作為填充必須清零
  }; 
  1. IPv4地址結構
// internet address  
struct in_addr
{
     in_addr_t  s_addr;            // u32 network address 
};

3,把主動套接字變成被動套接字listen()

int listen (int sockfd, int backlog);

  1. sockfd:監聽連線的套接字,通過socket()函式拿到的fd
  2. backlog
    ·指定了正在等待連線的最大佇列長度,它的作用在於處理可能同時出現的幾個連線請求。
    ·同時允許幾路客戶端和伺服器進行正在連線的過程(正在三次握手)一般填5, 測試得知,ARM最大為8
    ·DoS(拒絕服務)攻擊即利用了這個原理,非法的連線佔用了全部的連線數,造成正常的連線請求被拒絕。
  • 核心中伺服器的套接字fd會維護2個連結串列:
    1. 正在三次握手的的客戶端連結串列(數量=2*backlog+1)
    2. 已經建立好連線的客戶端連結串列(已經完成3次握手分配好了newfd)
  1. 返回值: 成功返回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) ;

  1. sockfd : 監聽套接字 ,經過前面socket()建立並通過bind(),listen()設定過的fd
  2. addr : 對方地址1(核心自動取到連線過來的客戶端的資訊)
  3. addrlen:地址長度
  4. 返回值:
    ·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()函式類似

  1. sockfd : socket返回的檔案描述符
  2. serv_addr : 伺服器端的地址資訊
  3. addrlen : serv_addr的長度
  4. 返回值: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);

  1. buffer : 傳送緩衝區首地址
  2. length : 傳送的位元組數
  3. flags : 傳送方式(通常為0,填0的時候和write()一樣)
flags 含義
MSG_DONTWAIT Enables nonblocking operation;非阻塞傳送
MSG_OOB 用以傳送TCP型別的帶外資料(out-of-band)
  1. 返回值:
    ·成功:實際傳送的位元組數
    ·失敗:-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);

  1. buffer : 傳送緩衝區首地址
  2. length : 傳送的位元組數
  3. 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. 返回值:
    ·成功:實際接收的位元組數
    ·失敗:-1, 並設定errno

8,套接字的關閉 close()、shutdown()

8.1,關閉雙向通訊 close()

int close(int sockfd);

8.2,選擇關閉 shutdown()

int shutdown(int sockfd, int howto);

  1. TCP連線是雙向的(是可讀寫的),當我們使用close時,會把讀寫通道都關閉,有時侯我們希望只關閉一個方向,這個時候我們可以使用shutdown。
  2. 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()效果一樣
  1. 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!