二,函數詳細介紹
1, int socket(int domain, int type,int protocol)
domain:說明我們網絡程序所在的主機采用的通訊協族(AF_UNIX和AF_INET等).
其實這里指定的是地址族,在unix中協議族和地址族是一一對應的.
AF_UNIX只能夠用于單一的Unix系統進程間通信,而AF_INET是針對Internet的,
因而可以允許在 遠程 主機之間通信(當我們 man socket時發現 domain可選項
是 PF_*而不是AF_*,因為glibc是posix的實現 所以用PF代替了AF,實際上查看
sys/socket.h會發現如下定義:#define AF_INET 2 ,#define PF_INET AF_INET).
type:我們網絡程序所采用的通訊協議(SOCK_STREAM,SOCK_DGRAM,SOCK_RAW等)
SOCK_STREAM表明我們用的是TCP協議,這樣會提供按順序的,可靠,雙向,面向連接的比特流.
SOCK_DGRAM 表明我們用的是UDP協議,這樣只會提供定長的,不可靠,無連接的通信.
SOCK_RAW提供對internal network interfaces的訪問,只有特權程序才能使用。對應IP協議、ICMP協議等等。
protocol:由于我們指定了type,所以對應了單一的protocal,所以這個地方我們一般只要用0來代替就可以了 .
如果存在多協議,則必須明確指定.
socket為網絡通訊做基本的準備.成功時返回一個socket號,失敗時返回-1.
server端和client端都使用本函數.
2, int bind(int sfd, struct sockaddr_in *addr, int len)
sfd:是由socket調用返回的描述符即socket號.
addr:是一個指向sockaddr_in的指針.
len:是sockaddr結構的長度. sizeof ( struct sockaddr )
在<linux/in.h>中sockaddr_in的定義為
struct sockaddr_in
{
unsigned short sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
sin_family一般為AF_INET,
sin_addr設置為INADDR_ANY表示可以和任何的主機通信,
sin_port是我們要監聽的端口號.
sin_zero[8]暫時未用
bind后將本地的端口同socket返回的描述符捆綁在一起.成功是返回0,失敗返回-1.
屬于server端函數.
3, int listen(int sfd,int qlen)
sfd:是server方的socket號.
qlen:設置請求排隊的最大長度.當有多個客戶端程序和服務端相連時, 使用這個表示
可以接收的排隊長度.一般我們都寫5. listen函數將bind的文件描述符變為監聽套接字.
成功返回0,失敗返回-1.本函數必須在aclearcase/" target="_blank" >ccept之前調用.
屬于server端函數.
4, int accept(int sfd, struct sockaddr_in *addr,int *len)
sfd:是server方的socket號.
addr,addrlen是用指針接收client端填寫的信息. 調用前都做一個初始化.調用時,
server端的程序會一直阻塞直到到有一個client調用connect函數請求了連接. accept
成功時返回一個非負整數表示accepted socket,這時server端可以向該描述符寫信息了.
失敗時返回-1 .
在并發服務器方式中,server 端fork出一個從服務器,從服務器利用調用返回的accepted
socket與client通信。
在面向連接的通信中,client可以不調用bind,connect會自動完成。
在無連接通信中,客戶方必須調用bind。connect主要是為面向連接通
信的客戶設計的,accept則完全是為面向連接通信的服務器設計的。
connect可以用于無連接的服務器以及客戶,其作用可以代替bind,
但比bind功能強大。
屬于server端函數.
5, int connect(int sfd, struct sockaddr_in *serv_addr,int addrlen)
sfd:socke fd.
serveraddr:儲存了server端的連接信息.描述了它的連接目的地.
addrlen:serveraddr的長度.
connect函數是client端連接server的.成功時返回0, 失敗時返回-1.
6, IP和域名的轉換的一組函數
struct hostent *gethostbyname(const char *hostname)
struct hostent *gethostbyaddr(const char *addr,int len,int type)
struct hostent *gethostbyname_r(const char *name, struct hostent *result,
char *buffer, int buflen, int *h_errnop);
struct hostent *gethostbyaddr_r(const char *addr, int length, int type,
struct hostent *result, char *buffer,
int buflen, int *h_errnop);
在<netdb.h>中有struct hostent的定義
struct hostent
{
char *h_name; /* 主機的名稱 */
char *h_aliases; /* 主機的別名 */
int h_addrtype; /* 主機的地址類型 AF_INET*/
int h_length; /* 主機的地址長度 對于IP4 是4字節32位*/
char **h_addr_list; /* 主機的IP地址列表 */
}
gethostbyname可以將機器名(如 Diablo )轉換為一個結構指針.在這個結構里面儲存了域名的信息
gethostbyaddr可以將一個32位的IP地址(C0A80001)轉換為結構指針.
gethostbyname()、gethostbyaddr()使用了靜態數據區,這些靜態數據區會在每次函數調用
中都使用到,在多線程應用中使用這些函數是有問題的。
失敗時均返回NULL 且設置h_errno,調用h_strerror()可以得到詳細錯誤信息.
gethostbyname_r()、gethostbyaddr_r()是支持重入的版本。參數result必須是一個指向
struct hostent的指針,該結構所用內存空間必須是調用者明確分配下來的。若成功,主
機描述信息將返回到這個結構中。參數buffer必須指向由調用者提供的緩沖空間。buffer
用于存放主機數據,返回值struct hostent 中所有的指針均指向存放在buffer中的數據。
buffer必須足夠大以致能存放所有可能返回的數據。參數buflen給出buffer的字節大小。
參數h_errnop是一個整型指針,發生錯誤時這里存放了錯誤信息。
7, 字節轉換函數
unsigned long int htonl(unsigned long int hostlong)
unsigned short int htons(unisgned short int hostshort)
unsigned long int ntohl(unsigned long int netlong)
unsigned short int ntohs(unsigned short int netshort)
h 代表host, n 代表 network.s 代表short l 代表long
第一個函數的意義是將本機上的long型轉化為網絡上的long型.
第二個函數的意義是將本機上的short型轉化為網絡上的short型.
第三個函數的意義是將網絡上的long型轉化為本機上的long型.
第四個函數的意義是將網絡上的short型轉化為本機上的short型.
為什么會有這樣幾個函數呢?是因為在網絡的機器在表示數據的字節順序是不同的,為了統一起來,
有了專門的字節轉換函數.
其實這些函數大多數是用作關聯gethostbyname和getservent返回的地址和端口號的.
8, IP處理系列函數
char *inet_ntoa(struct in_addr in)
int inet_aton(const char *cp,struct in_addr *inp)
函數里面 a 代表 ASCII ,n 代表network.
第一個函數是將32位網絡IP轉換為a.b.c.d的ASCII格式.
第二個函數如果地址有效,會返回1,否則函數返回0.
9, 服務信息函數
int getsockname(int sockfd,struct sockaddr *localaddr,int *addrlen)
int getpeername(int sockfd,struct sockaddr *peeraddr, int *addrlen)
struct servent *getservbyname(const char *servname,const char *protoname)
struct servent *getservbyport(int port,const char *protoname)
struct servent
{
char *s_name; /* 正式服務名 */
char **s_aliases; /* 別名列表 */
int s_port; /* 端口號 */
char *s_proto; /* 使用的協議 */
}
一般我們很少用這幾個函數.對應客戶端,當我們要得到連接的端口號時在connect調用成
功后使用可得到 系統分配的端口號.對于服務端,我們用INADDR_ANY填充后,為了得到連
接的IP我們可以在accept調用成功后 使用而得到IP地址.
在網絡上有許多的默認端口和服務,比如端口21對ftp80對應WWW.為了得到指定的端口號
的服務 我們可以調用第四個函數,相反為了得到端口號可以調用第三個函數.
10 , 讀寫函數
寫函數
ssize_t write(int sfd,const void *buf,size_t nbytes)
write函數將buf中的nbytes字節內容寫入描述符sfd.
成功時返回寫的字節數.失敗時返回-1. 并設置errno變量.
1)write的返回值大于0,表示寫了部分或者是全部的數據.
2)返回的值小于0,此時出現了錯誤.我們要根據錯誤類型來處理.
如果錯誤為EINTR表示在寫的時候出現了中斷錯誤.
如果為EPIPE表示網絡連接出現了問題(對方已經關閉了連接).
讀函數
ssize_t read(int fd,void *buf,size_t nbyte)
read函數是負責從fd中讀取內容.當讀成功時,read返回實際所讀的字節數,
如果返回的值是0 表示已經讀到文件的結束了,小于0表示出現了錯誤.
如果錯誤為EINTR說明讀是由中斷引起的, 如果是ECONNREST表示網絡
連接出了問題.
其實大多數時候我們都是傳遞一個報文,一般我們會把一個結構memcpy到一個
buffer中,然后將這個buffer str在網絡上傳遞.
11 , UDP函數
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr * from ,int *fromlen)
int sendto(int sockfd,const void *msg,int len,unsigned int flags,struct sockaddr *to ,int tolen)
sockfd,buf,len的意義和read,write一樣,分別表示套接字描述符,發送或接收的緩沖區及大小.
recvfrom負責從sockfd接收數據,如果from不是NULL,那么在from里面存儲了信息來源的情況,
如果對信息的來源不感興趣,可以將from和fromlen設置為NULL.sendto負責向to發送信息.
此時在to里面存儲了收信息方的詳細資料.
12 , 高級套接字函數 之recv和send
int recv(int sockfd,void *buf,int len,int flags)
int send(int sockfd,void *buf,int len,int flags)
recv和send函數提供了和read和write差不多的功能.不過它們提供了第四個參數來控制
讀寫操作.
前面的三個參數和read,write一樣,第四個參數可以是0或者是以下的組合
MSG_DONTROUTE,不查找路由表
MSG_OOB,接受或者發送帶外數據
MSG_PEEK,查看數據,并不從系統緩沖區移走數據
MSG_WAITALL,等待所有數據
MSG_DONTROUTE:是send函數使用的標志.這個標志告訴IP協議.目的主機在本地網絡上面
,沒有必要查找路由表.這個標志一般用網絡診斷和路由程序里面.
MSG_OOB:是send函數使用的標志.表示可以接收和發送帶外的數據.
MSG_PEEK:是recv函數的使用標志,表示只是從系統緩沖區中讀取內容,而不清楚系統緩沖
區的內容.這樣下次讀的時候,仍然是一樣的內容.一般在有多個進程讀寫數據時可以使用
這個標志.
MSG_WAITALL是recv函數的使用標志,表示等到所有的信息到達時才返回.使用這個標志的
時候recv回一直阻塞,直到指定的條件滿足,或者是發生了錯誤.
1)當讀到了指定的字節時,函數正常返回.返回值等于len
2)當讀到了文件的結尾時,函數正常返回.返回值小于len
3)當操作發生錯誤時,返回-1,且設置錯誤為相應的錯誤號(errno)
如果flags為0,則和read,write一樣的操作.上面介紹的是我們常用的幾個參數,當然還有其它
的幾個選項,不過我們實際上用的很少.
13 ,高級套接字函數 之recvmsg和sendmsg
recvmsg和sendmsg可以實現前面所有的讀寫函數的功能.
int recvmsg(int sockfd,struct msghdr *msg,int flags)
int sendmsg(int sockfd,struct msghdr *msg,int flags)
struct msghdr
{
void *msg_name;
int msg_namelen;
struct iovec *msg_iov;
int msg_iovlen;
void *msg_control;
int msg_controllen;
int msg_flags;
}
struct iovec
{
void *iov_base; /* 緩沖區開始的地址 */
size_t iov_len; /* 緩沖區的長度 */
}
msg_name和 msg_namelen當套接字是非面向連接時(UDP),它們存儲接收和發送方的地址
信息.msg_name實際上是一個指向struct sockaddr的指針,msg_namelen是結構的長度.
當套 接字是面向連接時,這兩個值應設為NULL.
msg_iov和msg_iovlen指出接受和發送的緩沖區內容.msg_iov是一個結構指針,msg_iovlen
指出這個結構數組的大小.
msg_control和msg_controllen這兩個變量是用來接收和發送控制數據時的 ??
msg_flags指定接受和發送的操作選項,和recv,send的選項一樣.
14 ,高級套接字函數 之shutdown
int shutdown(int sockfd,int howto)
TCP連接是雙向的(是可讀寫的),當我們使用close時,會把讀寫通道都關閉,有時侯我們希
望只關閉一個方向,這個時候我們可以使用shutdown.針對不同的howto,系統會采取不同
的關閉方式.
howto=0這個時候系統會關閉讀通道.但是可以繼續往接字描述符寫.
howto=1關閉寫通道,和上面相反,這時候就只可以讀了.
howto=2關閉讀寫通道,和close一樣 .
在多進程程序里面,如果有幾個子進程共享一個套接字時,如果我們使用shutdown, 那么所
有的子進程都不能夠操作了,這個時候我們只能夠使用close來關閉子進程的套接字描述符.
15 ,高級套接字函數 getsockopt和setsockopt
int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen)
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)
level指定控制套接字的層次.可以取三種值:
1)SOL_SOCKET:通用套接字選項.
2)IPPROTO_IP:IP選項.
3)IPPROTO_TCP:TCP選項.
對應的optname詳細說明
optname指定控制的方式(選項的名稱).
選項名稱 說明 數據類型
========================================================================
SOL_SOCKET
SO_BROADCAST 允許發送廣播數據 int
SO_DEBUG 允許調試 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 獲得套接字錯誤 int
SO_KEEPALIVE 保持連接 int
SO_LINGER 延遲關閉連接 struct linger
SO_OOBINLINE 帶外數據放入正常數據流 int
SO_RCVBUF 接收緩沖區大小 int
SO_SNDBUF 發送緩沖區大小 int
SO_RCVLOWAT 接收緩沖區下限 int
SO_SNDLOWAT 發送緩沖區下限 int
SO_RCVTIMEO 接收超時 struct timeval
SO_SNDTIMEO 發送超時 struct timeval
SO_REUSERADDR 允許重用本地地址和端口 int
SO_TYPE 獲得套接字類型 int
SO_BSDCOMPAT 與BSD系統兼容 int
IPPROTO_IP
IP_HDRINCL 在數據包中包含IP首部 int
IP_OPTINOS IP首部選項 int
IP_TOS 服務類型
IP_TTL 生存時間 int
IPPRO_TCP
TCP_MAXSEG TCP最大數據段的大小 int
TCP_NODELAY 不使用Nagle算法 int
=========================================================================
optval獲得或者是設置套接字選項.ON或者OFF等 .
16 ,高級套接字函數 ioctl
int ioctl(int fd,int req,...)
ioctl可以控制所有的文件描述符的情況,這里介紹一下控制套接字的選項.
ioctl的控制選項
SIOCATMARK 是否到達帶外標記 int
FIOASYNC 異步輸入/輸出標志 int
FIONREAD 緩沖區可讀的字節數 int
17 , 高級套接字函數 select
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout)
void FD_SET(int fd,fd_set *fdset)
void FD_CLR(int fd,fd_set *fdset)
void FD_ZERO(fd_set *fdset)
int FD_ISSET(int fd,fd_set *fdset)
一般的來說當我們在向文件讀寫時,進程有可能在讀寫出阻塞,直到一定的條件滿足. 比
如我們從一個套接字讀數據時,可能緩沖區里面沒有數據可讀(通信的對方還沒有 發送數
據過來),這個時候我們的讀調用就會等待(阻塞)直到有數據可讀.如果我們不 希望阻塞
,我們的一個選擇是用select系統調用. 只要我們設置好select的各個參數,那么當文件
可以讀寫的時候select回"通知"我們說可以讀寫了.
readfds所有要讀的文件文件描述符的集合
writefds所有要的寫文件文件描述符的集合
exceptfds其他的服要向我們通知的文件描述符
timeout超時設置.
nfds所有我們監控的文件描述符中最大的那一個加1
在我們調用select時進程會一直阻塞直到以下的一種情況發生.
1)有文件可以讀.
2)有文件可以寫.
3)超時所設置的時間到.
為了設置文件描述符我們要使用幾個宏.
FD_SET將fd加入到fdset
FD_CLR將fd從fdset里面清除
FD_ZERO從fdset中清除所有的文件描述符
FD_ISSET判斷fd是否在fdset集合中
.......................................等等函數.