apue讀書筆記之socket

 

創建 Socket:

 

#include <sys/socket.h>

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

 

domain: AF_INET AF_UNIX(AF_LOCAL 在某些系統) 規定通信的本質,比如地址的格式

type: SOCK_STREAM SOCK_DGRAM SOCK_RAW SOCK_SEQPACKET 規定數據的傳輸方式

protocol: 通常爲0,使用默認值。

 

四種 type 區別:

感覺聽上去很簡單,其實還是需要理解的

  • SOCK_DGRAM: 數據報方式。無連接,發出去的數據不保證能收到,但是收到的字節數和發出去的肯定相等。
  • SOCK_STREAM: 面向連接的流。注意是流,所以一次收到的字節數和一次發出的字節數不一定相等。有可能一次發出的數據,要分幾次收到;有可能幾次發出的數據,一次都能收到。但是肯定是面向連接的。消息之間沒有界限。
  • SOCK_SEQPACKET: 面向連接的基於消息的傳輸。消息之間是有界限的。協議: SCTP
  • SOCK_RAW: 數據報接口,面向IP層,需要自己添加協議頭什麼的。需要權限

 

Socket 常用的普通IO函數:

close fcntl ioctl select poll dup/dup2 read/readv write/writev

 

關閉 Socket:

close 直接關閉

shutdown 可以關閉socket某個方向的傳輸

 

#include <sys/socket.h>

int shutdown (int sockfd, int how);

Returns: 0 if OK, 1 on error

how: SHUT_RD/SHUT_WR/SHUT_RDWR

 

爲什麼需要shutdown?

  • close 需要等待所有引用的描述符都關閉才最終關閉,shutdown不會
  • shutdown可以關閉指定方向的傳輸

 

設置socket選項

#include <sys/socket.h>

int setsockopt(int sockfd, int level, int option, const void *val,socklen_t len);

Returns: 0 if OK, 1 on error

int getsockopt(int sockfd, int level, int option, void *restrict val, socklen_t *restrict lenp);

 

常用的選項:

SO_REUSEADDR 防止服務器突然重啓時重新綁定失敗。

 

地址格式

struct sockaddr {

     sa_family_t   sa_family;   /* address family */

     char          sa_data[];   /* variable-length address */

     .

     .

     .

   };

 

這個是POSIX規定的格式,系統調用用的都是這個接口,所以需要強制轉換。不同的平臺會有不同的實現方式。

 

AF_INET 地址格式:

#include <netinet/in.h>

struct in_addr {
     in_addr_t       s_addr;       /* IPv4 address */
   };

struct sockaddr_in {
     sa_family_t    sin_family;   /* address family */
     in_port_t      sin_port;     /* port number */
     struct in_addr sin_addr;     /* IPv4 address */
   };

 

上面是 Single UNIX Specification 規定的標準,每個平臺有自己的實現。Linux平臺的實現如下:

struct sockaddr_in {
     sa_family_t     sin_family;     /* address family */
     in_port_t       sin_port;       /* port number */
     struct in_addr  sin_addr;       /* IPv4 address */
     unsigned char   sin_zero[8];    /* filler */
   };

其中 sin_zero 必須全部設爲0.

 

地址和可讀字符串之間的轉換:

#include <arpa/inet.h>

const char *inet_ntop(int domain, const void *restrict addr, char *restrict str, socklen_t size);

Returns: pointer to address string on success, NULL on error

int inet_pton(int domain, const char *restrict str, void *restrict addr);

Returns: 1 on success, 0 if the format is invalid, or -1 on error

 

size 表示能容納輸出字符的數組大小(INET_ADDRSTRLEN  或者 INET6_ADDRSTRLEN )

domain 只能是 AF_INET 或者 AF_INET6

 

地址查找:

最常用的函數:

 

#include <sys/socket.h>

#include <netdb.h>

int getaddrinfo(const char *restrict host, const char *restrict service, const struct addrinfo *restrict hint, struct addrinfo **restrict res);

Returns: 0 if OK, nonzero error code on error

void freeaddrinfo(struct addrinfo *ai);

 

返回錯誤的時候錯誤信息由下面的函數取得:

 

#include <netdb.h>

const char *gai_strerror(int error);

error 函數的返回值

 

getnameinfo 通過地址來查找主機名和服務名:

#include <sys/socket.h>

#include <netdb.h>

int getnameinfo(const struct sockaddr *restrict addr, socklen_t alen, char *restrict host, socklen_t hostlen, char *restrict service, socklen_t servlen, unsigned int flags);

Returns: 0 if OK, nonzero on error

 

注意:

host 指向 主機名稱或者 ip地址

service 指向服務名稱或者端口號 (/etc/drivers 裏面有服務名稱到端口號的映射)

填充 hint 會縮小查詢結構的範圍

注意 flag 參數的選取。

 

綁定地址

系統調用:

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t len);

Returns: 0 if OK

 

: addr 參數一般是有 sockaddr_in 強制轉換來的, len參數一般是sizeof 來獲取的。

 

AF_INET 中,地址被指定 INADDR_ANY 將綁定本機所有的IP地址的對應端口。

 

獲取socket綁定的地址:

#include <sys/unistd.h>

 

int getsockname (int sockfd, struct sockaddr *localaddr, int *addrlen);

獲取本機

int getpeername(int sockfd, struct sockaddr *peeraddr, int *addrlen);

獲取對方

注意: getpeername 只能在面向連接的連接中使用。在UDP中調用recvfrom函數會返回對方的地址。

 

建立連接:

 

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t len);

Returns: 0 if OK

 

:

在非阻塞模式中,如果不能馬上建立連接,將返回1errno設置爲EINPROGRESS. 程序可以使用select或者poll來確定socket什麼時候是可寫的。

 

connect可以用在UDP中,這樣以後發數據的時候就不用每次都指定發送地址。

 

監聽:

 

#include <sys/socket.h>

int listen(int sockfd, int backlog);

Returns: 0 if OK, 1 on error

 

backlog 指定最大允許接入的連接數量。

 

接入連接:

 

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);

Returns: file (socket) descriptor if OK, 1 on error

 

返回接入的socket 描述符。addr 返回接入的地址和端口號,注意接入的客戶地址中的端口號是對方主機隨即分配的哦。

 

數據傳輸

發送: write / send / sendto / sendmsg

接收: read / recv / recvfrom / recvmsg

 

注意:

sendto recvfrom 一般用在 UDP

一些常用的flags: MSG_OOB MSG_PEEK MSG_NOBLOCK

 

帶外數據(Out of band data)

TCP支持,緊急數據,高優先級發送。

帶外數據只支持一個字節,發送的時候指定 MSG_OOB

 

接收到數據的時候,產生 SIGURG 信號,但是必須先進行設置才能接收到信號:

  • fcntl(sockfd, F_SETOWN, pid); 設置socket所屬的進程。F_GETOWN可以得到socket所屬的進程或進程組

 

關於 urgent mark:

TCP支持在普通數據流中接受緊急數據。必須設置socket的選項爲 SO_OOBINLINE.

可以使用sockatmark來判斷當前是否在讀urgent mark

 

#include <sys/socket.h>

int sockatmark(int sockfd);

Returns: 1 if at mark, 0 if not at mark, -1 on error

 

最後

  • TCP可以在正常數據中接收OOB,也可以單獨recv(指定MSG_OOB).
  • TCP 只支持一個字節的OOB,後面的會沖掉前面未接收的

 

關於非阻塞和異步

設置費阻塞模式:

fcntl 打開O_NOBLOCK 開關,這樣send recv 暫時不能執行的時候返回錯誤,errno設置EWOULDBLOCK 或者 EAGAIN. 然後可以用poll/select/epoll來查詢什麼時候可以寫或讀。

 

異步socket:

socket可讀或者可寫的時候會發信號SIGIO,估計也要先設置socket的所屬進程 F_SETOWN

啓用異步的步驟:

  • 設置socket所屬進程
    • fcntl(fd, F_SETOWN, pid)
    • ioctl(fd, FIOSETOWN, pid)
    • ioctl(fd, SIOCSPGRP, pid)
  • socket啓用異步
    • fcntl(fd, F_SETFL, flags|O_ASYNC)
    • ioctl(fd, FIOASYNC, &n)

推薦使用fcntl,貌似所有平臺都支持。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章