當(dāng)前位置:首頁(yè) > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > 基于TCP/UDP的Socket編程
---- socket概述:
socket是在應(yīng)用層和傳輸層之間的一個(gè)抽象層,它把TCP/IP層復(fù)雜的操作抽象為幾個(gè)簡(jiǎn)單的接口供應(yīng)用層調(diào)用已實(shí)現(xiàn)進(jìn)程在網(wǎng)絡(luò)中通信。
socket起源于UNIX,在Unix一切皆文件哲學(xué)的思想下,socket是一種"打開(kāi)—讀/寫(xiě)—關(guān)閉"模式的實(shí)現(xiàn),服務(wù)器和客戶(hù)端各自維護(hù)一個(gè)"文件",在建立連接打開(kāi)后,可以向自己文件寫(xiě)入內(nèi)容供對(duì)方讀取或者讀取對(duì)方內(nèi)容,通訊結(jié)束時(shí)關(guān)閉文件。
---- 接口簡(jiǎn)介:
socket():創(chuàng)建socket
bind():綁定socket到本地地址和端口,通常由服務(wù)端調(diào)用
listen():TCP專(zhuān)用,開(kāi)啟監(jiān)聽(tīng)模式
accept():TCP專(zhuān)用,服務(wù)器等待客戶(hù)端連接,一般是阻塞態(tài)
connect():TCP專(zhuān)用,客戶(hù)端主動(dòng)連接服務(wù)器
send():TCP專(zhuān)用,發(fā)送數(shù)據(jù)
recv():TCP專(zhuān)用,接收數(shù)據(jù)
sendto():UDP專(zhuān)用,發(fā)送數(shù)據(jù)到指定的IP地址和端口
recvfrom():UDP專(zhuān)用,接收數(shù)據(jù),返回?cái)?shù)據(jù)遠(yuǎn)端的IP地址和端口
closesocket():關(guān)閉socket
---- 流程如下:
接口詳解,常用的系統(tǒng)調(diào)用如下:
>> 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 -- 數(shù)據(jù)報(bào)(datagram),無(wú)連接的,no logical connection exist between peers for them to communicate. A datagram is a self-contained(獨(dú)立的) message. 類(lèi)似于(analogous)發(fā)郵件,你可以發(fā)送多封郵件,但是不能保證郵件是否到達(dá)和郵件到達(dá)的順序。每一封郵件都包含了接收者的地址。
TCP -- 字節(jié)流 A byte stream(SOCK_STREAM), in contrast, 在傳輸數(shù)據(jù)之前,需要建立連接,面向連接的通信類(lèi)似于打電話。點(diǎn)到點(diǎn)的連接里包含了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.
本函數(shù)從s的等待連接隊(duì)列中抽取第一個(gè)連接,創(chuàng)建一個(gè)與s同類(lèi)的新的套接口并返回句柄。如果隊(duì)列中無(wú)等待連接,且套接口為阻塞方式,則accept()阻塞調(diào)用進(jìn)程直至新的連接出現(xiàn)。如果套接口為非阻塞方式且隊(duì)列中無(wú)等待連接,則accept()返回一錯(cuò)誤代碼WSAEWOULDBLOCK。已接受連接的套接口不能用于接受新的連接,原監(jiān)聽(tīng)套接口仍保持開(kāi)放。
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.(客戶(hù)端)
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 (指向一個(gè)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類(lèi)型的數(shù)據(jù)發(fā)送
原型:int send(int sockfd, const void * msg, int len, int flags);
每個(gè)TCP套接口都有一個(gè)發(fā)送緩沖區(qū),它的大小可以用SO_SNDBUF這個(gè)選項(xiàng)來(lái)改變。調(diào)用send函數(shù)的過(guò)程實(shí)際是內(nèi)核將用戶(hù)數(shù)據(jù)(msg)拷貝至TCP套接口的發(fā)送緩沖區(qū)的過(guò)程。若len大于發(fā)送緩沖區(qū)的大小,則返回-1. 否則,查看緩沖區(qū)剩余空間是否容納得下要發(fā)送的len長(zhǎng)度,若不夠,則拷貝一部分,并返回拷貝長(zhǎng)度(指的是非阻塞send,若為阻塞send,則一定等待所有數(shù)據(jù)拷貝至緩沖區(qū)才返回,因此阻塞send返回值必定與len相等)。若緩沖區(qū)滿(mǎn),則等待發(fā)送,有剩余空間后拷貝至緩沖區(qū),若在拷貝過(guò)程中出現(xiàn)錯(cuò)誤,則返回-1.關(guān)于錯(cuò)誤的原因,查看errno的值。
注意:send成功返回并不代表對(duì)方已接收到數(shù)據(jù),如果后續(xù)的協(xié)議傳輸過(guò)程中出現(xiàn)網(wǎng)絡(luò)錯(cuò)誤,下一個(gè)send便會(huì)返回-1發(fā)送錯(cuò)誤。TCP給對(duì)方的數(shù)據(jù)必須在對(duì)方給予確認(rèn)時(shí),方可刪除發(fā)送緩沖區(qū)的數(shù)據(jù)。否則,會(huì)一直緩存到緩沖區(qū)直至發(fā)送成功。
參數(shù)解釋?zhuān)?/p>
sockfd -- 發(fā)送端套接字描述符 (非監(jiān)聽(tīng)描述符)
msg -- 待發(fā)送數(shù)據(jù)的緩沖區(qū) (將其內(nèi)容的len長(zhǎng)度拷貝到socket的發(fā)送緩沖區(qū))
len -- 待發(fā)送數(shù)據(jù)的字節(jié)長(zhǎng)度。
flags -- 一般情況下置為0.
>> recv() : TCP類(lèi)型的數(shù)據(jù)接收
原型:int recv(int sockfd, void *buf, int len, unsigned int flags);
recv()從接收緩沖區(qū)拷貝數(shù)據(jù)。成功時(shí),返回拷貝的字節(jié)數(shù),失敗時(shí),返回-1。阻塞模式下,recv()將會(huì)阻塞直到緩沖區(qū)中至少有一個(gè)字節(jié)才返回,沒(méi)有數(shù)據(jù)時(shí)處于休眠狀態(tài)。若非阻塞,則立即返回,有數(shù)據(jù)則返回拷貝的數(shù)據(jù)大小,否則返回錯(cuò)誤-1.
參數(shù)解釋?zhuān)?/p>
sockfd -- 接收端套接字描述符(非監(jiān)聽(tīng)描述符)
buf -- 接收數(shù)據(jù)的基地址(將socket的接收緩沖區(qū)中的內(nèi)容拷貝至buf中)
len -- 接收到數(shù)據(jù)的字節(jié)數(shù)
flags -- 一般情況下置為0.
>> sendto() : UDP類(lèi)型的數(shù)據(jù)發(fā)送
原型:int sendto(int sockfd, const void * msg, int len, unsigned int flags, const struct sockaddr * dst_addr, int addrlen);
用于非可靠連接(UDP)的數(shù)據(jù)發(fā)送,因?yàn)閁DP方式未建立連接socket,因此需要指定目的socket的address。
可使用同一個(gè)UDP套接口描述符sockfd和不同的目的地址通信。而TCP要預(yù)先建立連接,每個(gè)連接都會(huì)產(chǎn)生不同的套接口描述符,體現(xiàn)在:客戶(hù)端要使用不同的fd進(jìn)行connect,服務(wù)端每次accept產(chǎn)生不同的fd。
UDP沒(méi)有真正的發(fā)送緩沖區(qū),因?yàn)槭遣豢煽窟B接,不必保存應(yīng)用進(jìn)程的數(shù)據(jù)拷貝,應(yīng)用進(jìn)程的數(shù)據(jù)在沿協(xié)議棧向下傳遞時(shí),以某種形式拷貝到內(nèi)核緩沖區(qū),當(dāng)數(shù)據(jù)鏈路層把數(shù)據(jù)傳出后就把內(nèi)核緩沖區(qū)中數(shù)據(jù)拷貝刪除。因此它不需要一個(gè)發(fā)送緩沖區(qū)。
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類(lèi)型的數(shù)據(jù)接收
原型:int recvfrom(int sockfd, void * buf, size_t len, int flags, struct sockaddr * src_addr, int * addrlen);
參數(shù)解釋?zhuān)?/p>
sockfd -- 接收端套接字描述;
buf -- 用于接收數(shù)據(jù)的應(yīng)用緩沖區(qū)地址;
len -- 指明緩沖區(qū)大小;
flags -- 通常為0;
src_addr -- 數(shù)據(jù)來(lái)源端的地址(IP address,Port number).
fromlen -- 作為輸入時(shí),fromlen常常設(shè)置為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(在調(diào)用之前), addrlen should be initialized to the size of the structure pointed to by src_addr(結(jié)構(gòu)的大。; upon return(在返回時(shí)), it contains the number of bytes actually written to this structure.