---- socket概述:
socket是在應用層和傳輸層之間的一個抽象層,它把TCP/IP層復雜的操作抽象為幾個簡單的接口供應用層調用已實現進程在網絡中通信。
socket起源于UNIX,在Unix一切皆文件哲學的思想下,socket是一種"打開—讀/寫—關閉"模式的實現,服務器和客戶端各自維護一個"文件",在建立連接打開后,可以向自己文件寫入內容供對方讀取或者讀取對方內容,通訊結束時關閉文件。
---- 接口簡介:
socket():創建socket
bind():綁定socket到本地地址和端口,通常由服務端調用
listen():TCP專用,開啟監聽模式
accept():TCP專用,服務器等待客戶端連接,一般是阻塞態
connect():TCP專用,客戶端主動連接服務器
send():TCP專用,發送數據
recv():TCP專用,接收數據
sendto():UDP專用,發送數據到指定的IP地址和端口
recvfrom():UDP專用,接收數據,返回數據遠端的IP地址和端口
closesocket():關閉socket
---- 流程如下:
接口詳解,常用的系統調用如下:
>> socket() : creating a socket
A socket is an abstraction of a communication endpoint. Just as they would use file descriptors to access files, applications use socket descriptors to access sockets. To create a socket, we call the socket() function.
原型:int socket(int domain, int type, int protocol);
返回值: returns file (socket) descriptor if OK, -1 on error.
domain:AF_INET, AF_INET6, AF_UNIX, AF_UNSPEC (address format)
type:SOCK_DGRAM, SOCK_RAW, SOCK_STREAM, SOCK_SEQPACKET
protocol:IPPROTO_IP, IPPROTO_IPV6, IPPROTO_TCP, IPPROTO_UDP
The protocol argument is usually zero, to select the default protocol for the given domain and socket type. The default protocol for a SOCK_STREAM socket in the AF_INET communication domain is TCP(Transmission Control Protocol). The default protocol for a SOCK_DGRAM socket in the AF_INET communication domain is UDP(User Datagram Protocol).
NOTE: UDP -- 數據報(datagram),無連接的,no logical connection exist between peers for them to communicate. A datagram is a self-contained(獨立的) message. 類似于(analogous)發郵件,你可以發送多封郵件,但是不能保證郵件是否到達和郵件到達的順序。每一封郵件都包含了接收者的地址。
TCP -- 字節流 A byte stream(SOCK_STREAM), in contrast, 在傳輸數據之前,需要建立連接,面向連接的通信類似于打電話。點到點的連接里包含了source and destination。
Communication on a socket is bidirectional. We can disable I/O on a socket with the shutdown function.
>> shutdown()
原型:int shutdown(int sockfd, int how);
返回值: returns 0 if OK, -1 on error.
The shutdown() system call closes one or both channels of the socket sockfd, depending on the value of how, which is specified as one of the following:
how: SHUT_RD, then reading from the socket is disabled. SHUT_WR, then we can't use the socket for transmitting data. We can use SHUT_RDWR to disable both data transmission and reception.
shutdown() differs from close() in another important respect: it closes the socket channels regardless of whether there are other file descriptors referring to the socket. For example, sockfd refers to a connected stream socket. If we make the following calls, then the connection remains open, and we can still perform I/O on the connection via the file descriptor fd2:
1. fd = dup(sockfd);
2. close(sockfd);
However, if we make the following sequence of calls, then both channels of the connection are closed, and I/O can no longer be performed via fd2:
1. fd2 = dup(sockfd);
2. shutdown(sockfd,SHUT_RDWR);
Note that shutdown() doesn't close the file descriptor, even if how is specified as SHUT_RDWR. To close the file descriptor, we must additionally call close().
>> bind() : binding a socket to an address
The bind() system call binds a socket to an address.
原型:int bind(int sockfd, const struct sockaddr * addr, socklen_t addrlen);
返回值:returns 0 on success, or -1 on error.
The sockfd argument is a file descriptor obtained from a previous call to socket(). The addr argument is a pointer to a structure specifying the address to which this socket is to be bound. The type of structure passed in this argument depends on the socket domain. The addrlen argument specifies the size of the address structure.
Typically, we bind a server's socket to a well-known address - that is, a fixed address that is known in advance to client applications that need to communicate with that server.
>> listen() : listening for incoming connections
原型:int listen(int sockfd, int backlog); // returns 0 on success, or -1 on error.
The listen() system call marks the stream socket referred to by the file descriptor sockfd as passive. The socket will subsequently be used to accept connections from other(active) sockets.
The client may call connect() before the server calls accept(). This could happen, for example, because the server is busy handling some other clients. This results in a pending connection, as illustrated in Figure 56-2.
The kernel must record some information about each pending connection request so that a subsequent accept() can be processed. The backlog argument allows us to limit the number of such pending connections. Further connection requests block until a pending connection is accepted(via accept()), and thus removed from the queue of pending connections.
>> accept() : accepting a connection
The accept() system call accepts an incoming connection on the listening stream socket referred to by the file descriptor sockfd. If there are no pending connections when accept() is called, the call blocks until a connection request arrives when the sockfd in block mode. If sockfd is in nonblocking mode, accept() will return -1 and set errno to either EAGAIN or EWOULDBLOCK.
原型:int accept(int sockfd, struct sockaddr * restrict addr, socklen_t * restrict len);
返回值:return file(socket) descriptor if OK, -1 on error.
本函數從s的等待連接隊列中抽取第一個連接,創建一個與s同類的新的套接口并返回句柄。如果隊列中無等待連接,且套接口為阻塞方式,則accept()阻塞調用進程直至新的連接出現。如果套接口為非阻塞方式且隊列中無等待連接,則accept()返回一錯誤代碼WSAEWOULDBLOCK。已接受連接的套接口不能用于接受新的連接,原監聽套接口仍保持開放。
The key point to understand about accept() is that it creates a new socket, and this new socket that is connected to the peer socket that performed the connect(). This new socket descriptor has the same socket type and address family as the original socket(sockfd). A file descriptor for the connected socket is returned as the function result of the accept() call. The listening socket(sockfd) remains open, and can be used to accept further connections. A typical server application creates one listening socket, binds it to a well-known address, and then handles all client requests by accepting connections via that socket.
The remaining(剩余的) arguments to accept() return the address of the peer socket.(客戶端)
If we don't care about the client's identity, we can set the addr and len parameters to NULL. Otherwise, before calling accept, we need to set the addr (指向一個buffer) parameter to a buffer large enough to hold the address and set the integer pointed to by len to the size of the buffer in bytes. On return, accept will fill in the client's address in the buffer and update the integer pointed to by len to reflect the size of the address.
>> connect() : connecting to a peer socket
原型:int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);
返回值: returns 0 on success, or -1 on error.
The connect() system call connects the active socket referred to by the file descriptor sockfd to the listening socket whose address is specified by addr and addrlen.
>> send() : TCP類型的數據發送
原型:int send(int sockfd, const void * msg, int len, int flags);
每個TCP套接口都有一個發送緩沖區,它的大小可以用SO_SNDBUF這個選項來改變。調用send函數的過程實際是內核將用戶數據(msg)拷貝至TCP套接口的發送緩沖區的過程。若len大于發送緩沖區的大小,則返回-1. 否則,查看緩沖區剩余空間是否容納得下要發送的len長度,若不夠,則拷貝一部分,并返回拷貝長度(指的是非阻塞send,若為阻塞send,則一定等待所有數據拷貝至緩沖區才返回,因此阻塞send返回值必定與len相等)。若緩沖區滿,則等待發送,有剩余空間后拷貝至緩沖區,若在拷貝過程中出現錯誤,則返回-1.關于錯誤的原因,查看errno的值。
注意:send成功返回并不代表對方已接收到數據,如果后續的協議傳輸過程中出現網絡錯誤,下一個send便會返回-1發送錯誤。TCP給對方的數據必須在對方給予確認時,方可刪除發送緩沖區的數據。否則,會一直緩存到緩沖區直至發送成功。
參數解釋:
sockfd -- 發送端套接字描述符 (非監聽描述符)
msg -- 待發送數據的緩沖區 (將其內容的len長度拷貝到socket的發送緩沖區)
len -- 待發送數據的字節長度。
flags -- 一般情況下置為0.
>> recv() : TCP類型的數據接收
原型:int recv(int sockfd, void *buf, int len, unsigned int flags);
recv()從接收緩沖區拷貝數據。成功時,返回拷貝的字節數,失敗時,返回-1。阻塞模式下,recv()將會阻塞直到緩沖區中至少有一個字節才返回,沒有數據時處于休眠狀態。若非阻塞,則立即返回,有數據則返回拷貝的數據大小,否則返回錯誤-1.
參數解釋:
sockfd -- 接收端套接字描述符(非監聽描述符)
buf -- 接收數據的基地址(將socket的接收緩沖區中的內容拷貝至buf中)
len -- 接收到數據的字節數
flags -- 一般情況下置為0.
>> sendto() : UDP類型的數據發送
原型:int sendto(int sockfd, const void * msg, int len, unsigned int flags, const struct sockaddr * dst_addr, int addrlen);
用于非可靠連接(UDP)的數據發送,因為UDP方式未建立連接socket,因此需要指定目的socket的address。
可使用同一個UDP套接口描述符sockfd和不同的目的地址通信。而TCP要預先建立連接,每個連接都會產生不同的套接口描述符,體現在:客戶端要使用不同的fd進行connect,服務端每次accept產生不同的fd。
UDP沒有真正的發送緩沖區,因為是不可靠連接,不必保存應用進程的數據拷貝,應用進程的數據在沿協議棧向下傳遞時,以某種形式拷貝到內核緩沖區,當數據鏈路層把數據傳出后就把內核緩沖區中數據拷貝刪除。因此它不需要一個發送緩沖區。
For sendto(), the dest_addr and addrlen arguments specify the socket to which the datagram is to be sent. These arguments are employed in the same manner as the corresponding arguments to connect(). The dest_addr argument is an address structure suitable for this communication domain. It is initialized with the address of the destination socket. The addrlen argument specifies the size of addr.
>> recvfrom() : UDP類型的數據接收
原型:int recvfrom(int sockfd, void * buf, size_t len, int flags, struct sockaddr * src_addr, int * addrlen);
參數解釋:
sockfd -- 接收端套接字描述;
buf -- 用于接收數據的應用緩沖區地址;
len -- 指明緩沖區大小;
flags -- 通常為0;
src_addr -- 數據來源端的地址(IP address,Port number).
fromlen -- 作為輸入時,fromlen常常設置為sizeof(struct sockaddr).
For recvfrom(), the src_addr and addrlen arguments return the address of the remote socket used to send the datagram. (These arguments are analogous to the addr and addrlen arguments of accept(), which return the address of a connecting peer socket.) Prior to the call(在調用之前), addrlen should be initialized to the size of the structure pointed to by src_addr(結構的大小); upon return(在返回時), it contains the number of bytes actually written to this structure.