国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

Home php教程 PHP開發(fā) Detailed explanation of SOCKET programming in Linux

Detailed explanation of SOCKET programming in Linux

Dec 13, 2016 am 10:30 AM

1. How to communicate between processes in the network

The concept of process communication originally came from stand-alone systems. Since each process runs within its own address range, in order to ensure that two mutually communicating processes do not interfere with each other and work in a coordinated manner, the operating system provides corresponding facilities for process communication, such as

UNIX BSD There are: pipe (pipe), named pipe (named pipe) soft interrupt signal (signal)

UNIX system V has: message (message), shared storage area (shared memory) and semaphore (semaphore), etc.

They all Only used for communication between native processes. Internet process communication aims to solve the problem of mutual communication between different host processes (process communication on the same machine can be regarded as a special case). To this end, the first thing to solve is the problem of process identification between networks. On the same host, different processes can be uniquely identified by process IDs. However, in a network environment, the process number assigned independently by each host cannot uniquely identify the process. For example, host A assigns process number 5 to a certain process, and process number 5 can also exist in host B. Therefore, the sentence "process number 5" is meaningless. Secondly, the operating system supports many network protocols, and different protocols work in different ways and have different address formats. Therefore, inter-network process communication must also solve the problem of identifying multiple protocols.

In fact, the TCP/IP protocol suite has helped us solve this problem. The "ip address" of the network layer can uniquely identify the host in the network, while the "protocol + port" of the transport layer can uniquely identify the application (process) in the host. ). In this way, the triplet (ip address, protocol, port) can be used to identify the network process, and process communication in the network can use this mark to interact with other processes.

Applications that use the TCP/IP protocol usually use application programming interfaces: sockets of UNIX BSD and TLI of UNIX System V (already obsolete) to achieve communication between network processes. For now, almost all applications use sockets, and now is the Internet era. Process communication on the network is ubiquitous. This is why I say "everything is socket".


2. What are TCP/IP and UDP

TCP/IP (Transmission Control Protocol/Internet Protocol) is a transmission control protocol/internet protocol. It is an industrial standard protocol set. It is designed for wide area networks (WANs).

The TCP/IP protocol exists in the OS, and network services are provided through the OS. System calls that support TCP/IP are added to the OS - Berkeley sockets, such as Socket, Connect, Send, Recv, etc.

UDP (User Data Protocol (User Datagram Protocol) is the protocol corresponding to TCP. It is a member of the TCP/IP protocol suite. As shown in the figure:

Detailed explanation of SOCKET programming in Linux The TCP/IP protocol suite includes the transport layer, network layer, and link layer, and the location of the socket is as shown in the figure. Socket is the intermediate software abstraction layer for communication between the application layer and the TCP/IP protocol suite.

Detailed explanation of SOCKET programming in Linux

3. What is Socket

1. Socket:

Socket originated from Unix, and one of the basic philosophies of Unix/Linux is that "everything is a file" and can be opened with " open –> read and write write/read –> close” mode to operate. Socket is an implementation of this mode. Socket is a special file, and some socket functions are operations on it (read/write IO, open, close).

To put it bluntly, Socket is the application layer and TCP/IP protocol family An intermediate software abstraction layer for communication, which is a set of interfaces. In the design mode, Socket is actually a facade mode, which hides the complex TCP/IP protocol family behind the Socket interface. For users, a set of simple interfaces is all, allowing Socket to organize data to comply with the specified protocol.


Note: In fact, socket does not have the concept of layers. It is just an application of the facade design pattern, which makes programming easier. It is a software abstraction layer. In network programming, we use a lot of sockets.

2. Socket descriptor

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? are actually just an integer. The three handles we are most familiar with are 0, 1, and 2. 0 is the standard input, 1 is the standard output, and 2 is the standard error output. 0, 1, and 2 are represented by integers, and the corresponding FILE * structures are represented by stdin, stdout, stderr


The socket API was originally developed as part of the UNIX operating system, so the socket API is the same as Other I/O devices of the system are integrated together. In particular, when an application creates a socket for Internet communication, the operating system returns a small integer as a descriptor to identify the socket. The application then passes the descriptor as a parameter and calls a function to complete some operation (such as transmitting data over the network or receiving incoming data).

In many operating systems, socket descriptors and other I/O descriptors are integrated, so applications can perform socket I/O or I/O read/write operations on files.

When an application wants to create a socket, the operating system returns a small integer as a descriptor, and the application uses this descriptor to refer to the socket. The application that requires I/O requests requests the operating system to open one. document. The operating system creates a file descriptor for the application to access the file. From an application's perspective, a file descriptor is an integer that an application can use to read and write files. The figure below shows how the operating system implements a file descriptor as an array of pointers that point to internal data structures.

Detailed explanation of SOCKET programming in Linux

There is a separate table for each program system. To be precise, the system maintains a separate file descriptor table for each running process. When a process opens a file, the system writes a pointer to the file's internal data structure into the file descriptor table and returns the index value of the table to the caller. The application only needs to remember this descriptor and use it when manipulating the file in the future. The operating system uses this descriptor as an index to access the process descriptor table, and uses the pointer to find the data structure that holds all the information about the file.

System data structure for sockets:

1). There is a function socket in the socket API, which is used to create a socket. The general idea of ??socket design is that a single system call can create any socket, since sockets are quite general. Once the socket is created, the application needs to call other functions to specify the specific details. For example, calling socket will create a new descriptor entry:

Detailed explanation of SOCKET programming in Linux

2) Although the internal data structure of the socket contains many fields, most of the fields are not filled in after the system creates the socket. After the application creates the socket, it must call other procedures to populate these fields before the socket can be used.

3. The difference between file descriptors and file pointers:

File descriptor: When you open a file in a Linux system, you will get a file descriptor, which is a small positive integer. Each process stores a file descriptor table in the PCB (Process Control Block). The file descriptor is the index of this table. Each table entry has a pointer to an open file.

File pointer: File pointer is used as the handle of I/O in C language. The file pointer points to a data structure called the FILE structure in the user area of ??the process. The FILE structure includes a buffer and a file descriptor. The file descriptor is an index into the file descriptor table, so in a sense the file pointer is the handle of the handle (on Windows systems, the file descriptor is called a file handle).

4. Basic SOCKET interface function

In life, A wants to call B, A dials the number, B hears the ringing tone and picks up the phone, then A and B establish a connection, A and B B can then speak. When the communication is over, hang up the phone to end the conversation. The call explained how this works in a simple way: "open-write/read-close" mode.

Detailed explanation of SOCKET programming in Linux

The server first initializes the Socket, then binds to the port, listens to the port, calls accept to block, and waits for the client to connect. At this time, if a client initializes a Socket and then connects to the server (connect), if the connection is successful, the connection between the client and the server is established. The client sends a data request, the server receives the request and processes the request, then sends the response data to the client, the client reads the data, and finally closes the connection, and the interaction ends.

The implementation of these interfaces is completed by the kernel. For details on how to implement it, you can look at the Linux kernel


4.1, socket() function

int socket(int protofamily, int type, int protocol);//Return sockfd

sockfd is the descriptor.

The socket function corresponds to the opening operation of an ordinary file. The ordinary file open operation returns a file descriptor, and socket() is used to create a socket descriptor (socket descriptor), which uniquely identifies a socket. This socket descriptor is the same as the file descriptor. It is used in subsequent operations. It is used as a parameter to perform some read and write operations.

Just like you can pass in different parameter values ??to fopen to open different files. When creating a socket, you can also specify different parameters to create different socket descriptors. The three parameters of the socket function are:

protofamily: that is, the protocol domain, also known as the protocol family (family). Commonly used protocol families include AF_INET (IPV4), AF_INET6 (IPV6), AF_LOCAL (or AF_UNIX, Unix domain socket), AF_ROUTE, etc. The protocol family determines the address type of the socket, and the corresponding address must be used in communication. For example, AF_INET determines to use a combination of ipv4 address (32-bit) and port number (16-bit), and AF_UNIX determines to use an absolute path. Name as address.

type: Specify the socket type. Commonly used socket types include SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_PACKET, SOCK_SEQPACKET, etc. (What are the types of sockets?).

protocol: hence the name, it means a designated protocol. Commonly used protocols include IPPROTO_TCP, IPPTOTO_UDP, IPPROTO_SCTP, IPPROTO_TIPC, etc., which respectively correspond to the TCP transmission protocol, UDP transmission protocol, STCP transmission protocol, and TIPC transmission protocol (I will discuss this protocol separately!).

Note: The above type and protocol cannot be combined at will. For example, SOCK_STREAM cannot be combined with IPPROTO_UDP. When protocol is 0, the default protocol corresponding to the type type is automatically selected.

When we call socket to create a socket, the returned socket descriptor exists in the protocol family (address family, AF_XXX) space, but does not have a specific address. If you want to assign an address to it, you must call the bind() function, otherwise the system will automatically assign a port randomly when calling connect() or listen().

4.2. bind() function

As mentioned above, the bind() function assigns a specific address in an address family to the socket. For example, corresponding to AF_INET and AF_INET6, an ipv4 or ipv6 address and port number combination is assigned to the socket.

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); The three parameters of the

function are:

sockfd: the socket descriptor, which is created through the socket() function and uniquely identifies it. a socket. The bind() function will bind a name to this descriptor.

addr: a const struct sockaddr * pointer pointing to the protocol address to be bound to sockfd. This address structure varies according to the address protocol family when the address creates the socket. For example, ipv4 corresponds to:

struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};

/* Internet address. The corresponding ipv6 is:


struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */

in_port_t sin6_port; /* port number */

uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */

uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */

};

struct in6_addr {
unsigned char s6_addr[16]; /* IPv6 address */
};


Unix domain corresponds to :

#define UNIX_PATH_MAX 108

struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};

addrlen: corresponds to the length of the address.

Usually the server will be bound to a well-known address (such as IP address + port number) when it is started to provide services, and customers can connect to the server through it; the client does not need to specify it, the system automatically assigns one Port number and its own IP address combination. This is why the server usually calls bind() before listening, but the client does not call it. Instead, the system randomly generates one during connect().

Network byte order and host byte order

Host byte order is what we usually call big endian and little endian modes: different CPUs have different byte order types, these byte order refers to the integers in memory The order in which they are saved is called host order. The standard definitions of Big-Endian and Little-Endian are quoted as follows:

 a) Little-Endian means that the low-order bytes are arranged at the low address end of the memory, and the high-order bytes are arranged at the high address end of the memory.

  b) Big-Endian means that the high-order bytes are arranged at the low address end of the memory, and the low-order bytes are arranged at the high address end of the memory.

Network byte order: 4-byte 32-bit values ??are transmitted in the following order: first 0~7bit, then 8~15bit, then 16~23bit, and finally 24~31bit. This transfer order is called big-endian. Because all binary integers in the TCP/IP header are required to be in this order when transmitted over the network, it is also called network byte order. Byte order, as the name suggests, is the order in which data larger than one byte is stored in memory. There is no order issue with data of one byte.

So: When binding an address to a socket, please first convert the host byte order to network byte order, and do not assume that the host byte order uses Big-Endian like the network byte order. There have been murders caused by this problem! This problem has caused many inexplicable problems in the company's project code, so please remember not to make any assumptions about the host byte order, and be sure to convert it into network byte order before assigning it to the socket.

4.3, listen(), connect() function

If you are a server, after calling socket(), bind(), listen() will be called to listen to the socket. If the client calls connect() at this time, it will issue Connection request, the server will receive this request.

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

The first parameter of the listen function is the socket descriptor to be listened to, and the second Each parameter is the maximum number of connections that can be queued by the corresponding socket. The socket created by the socket() function is an active type by default, and the listen function changes the socket to a passive type, waiting for the client's connection request.

The first parameter of the connect function is the client's socket descriptor, the second parameter is the server's socket address, and the third parameter is the length of the socket address. The client establishes a connection with the TCP server by calling the connect function.

4.4, accept() function

After the TCP server calls socket(), bind(), and listen() in sequence, it will listen to the specified socket address. After calling socket() and connect() in sequence, the TCP client sends a connection request to the TCP server. After the TCP server monitors this request, it will call the accept() function to receive the request, so that the connection is established. Then you can start network I/O operations, which are similar to ordinary file read and write I/O operations.

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //Return connection connect_fd

parameter sockfd

parameter sockfd is the listening socket explained above. This socket is used Listening on a port, when a client connects to the server, it uses this port number, and this port number is associated with this socket. Of course the client doesn't know the details of the socket, it only knows an address and a port number.

Parameter addr

This is a result parameter, which is used to accept a return value. This return value specifies the address of the client. Of course, this address is described through an address structure. The user should know what kind of address this is. structure. If you are not interested in the customer's address, you can set this value to NULL.

Parameter len

As everyone thinks, it is also a parameter of the result. It is used to accept the size of the above addr structure. It specifies the number of bytes occupied by the addr structure. Likewise, it can also be set to NULL.


If accept returns successfully, the server and client have correctly established a connection. At this time, the server completes communication with the client through the socket returned by accept.

Note:

Accept will block the process by default until a client connection is established and return. It returns a newly available socket, which is a connection socket.

At this point we need to distinguish between two types of sockets,

? ? ? Listening socket: The listening socket is just like the parameter sockfd of accept. It is a listening socket. After calling the listen function, the server starts to call the socket() function to generate it. It is called the listening socket descriptor (listening socket Word)

Connecting socket: A socket will transform from an actively connected socket to a listening socket; and the accept function returns the connected socket descriptor (a connected socket), which Represents a point-to-point connection that already exists on the network.

A server usually only creates a listening socket descriptor, which always exists during the life cycle of the server. The kernel creates a connected socket descriptor for each client connection accepted by the server process. When the server completes serving a client, the corresponding connected socket descriptor is closed.

The natural question to ask is: why are there two types of sockets? The reason is simple. If you use a descriptor, it has too many functions, making its use very unintuitive. At the same time, such a new descriptor is indeed generated in the kernel.

Connecting socket socketfd_new does not occupy a new port to communicate with the client. It still uses the same port number as the listening socket socketfd

4.5, read(), write() and other functions

Everything is ready All we owe is Dongfeng, and a connection between the server and the client has been established. Network I/O can be called for read and write operations, which means communication between different processes in the network is realized! Network I/O operations have the following groups:

read()/write()

recv()/send()

readv()/writev()

recvmsg()/sendmsg()

recvfrom( )/sendto()

I recommend using the recvmsg()/sendmsg() function. These two functions are the most common I/O functions. In fact, you can replace all the other functions above with these two functions. Their declarations are as follows:

? ? #include

? ssize_t read(int fd, void *buf, size_t count);
? ssize_t write(int fd, const void *buf, size_t count);

? # include
? ? ? ? #include

? ? ssize_t send(int sockfd, const void *buf, size_t len, int flags);
? ssize_t recv(int sockfd, void *buf , size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
? ? ? ? ? ? ? ? ? ? ? const struct sockaddr *dest_addr, socklen_t addrlen);
? s size_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recv msg(int sockfd, struct msghdr *msg, int flags);

The read function is responsible for reading content from fd. When the read is successful, read returns the actual number of bytes read. If the returned value is 0, it means that the end of the file has been read. If it is less than 0, it means that an error has occurred. . If the error is EINTR, it means that the read was caused by an interrupt. If it is ECONNREST, it means there is a problem with the network connection.

The write function writes the nbytes bytes content in buf to the file descriptor fd. When successful, it returns the number of bytes written. On failure, -1 is returned and the errno variable is set. In network programs, there are two possibilities when we write to the socket file descriptor. 1) The return value of write is greater than 0, indicating that part or all of the data has been written. 2) The returned value is less than 0, and an error occurred. We have to deal with it according to the error type. If the error is EINTR, it means that an interrupt error occurred during writing. If it is EPIPE, it means there is a problem with the network connection (the other party has closed the connection).

I will not introduce these pairs of I/O functions one by one. For details, please refer to the man document or Baidu or Google. Send/recv will be used in the following example.

4.6. close() function

After the server establishes a connection with the client, some read and write operations will be performed. After completing the read and write operations, the corresponding socket descriptor must be closed, such as calling fclose to close the opened file after the operation. open file.

#include
int close(int fd);

The default behavior of close is to mark the socket as closed and then immediately return to the calling process. This descriptor can no longer be used by the calling process, that is, it can no longer be used as the first parameter of read or write.

Note: The close operation only reduces the reference count of the corresponding socket descriptor by -1. Only when the reference count is 0 will the TCP client be triggered to send a termination request to the server.



5. Establishment of TCP in Socket (three-way handshake)

TCP protocol completes the establishment of the connection through three message segments. This process is called three-way handshake handshake), the process is shown in the figure below.


First handshake: When establishing a connection, the client sends a syn packet (syn=j) to the server and enters the SYN_SEND state, waiting for confirmation by the server; SYN: Synchronize Sequence Numbers.

Second handshake: The server receives the syn packet and must confirm the client's SYN (ack=j+1). At the same time, it also sends a SYN packet (syn=k), that is, SYN+ACK packet. At this time, the server enters SYN_RECV status;
Third handshake: The client receives the SYN+ACK packet from the server and sends a confirmation packet ACK (ack=k+1) to the server. After the packet is sent, the client and server enter the ESTABLISHED state, completed three times shake hands.
A complete three-way handshake is: request--response--confirm again.

Corresponding function interface:

Detailed explanation of SOCKET programming in Linux

As can be seen from the figure, when the client calls connect, a connection request is triggered and a SYN J packet is sent to the server. At this time, connect enters the blocking state; the server monitors the connection. Request, that is, receive the SYN J packet, call the accept function to receive the request and send SYN K, ACK J+1 to the client. At this time, accept enters the blocking state; after the client receives the server's SYN K, ACK J+1, at this time connect returns and confirms SYN K; when the server receives ACK K+1, accept returns. At this point, the three-way handshake is completed and the connection is established.


We can view the specific process through network packet capture:

For example, our server opens port 9502. Use tcpdump to capture packets:


tcpdump -iany tcp port 9502


Then we use telnet 127.0.0.1 9502 to open the connection.:

telnet 127.0.0.1 9502

14:12:45.104687 IP localhost.39870 > localhost.9502: Flags [S], seq 2927179378, win 32792, options [mss 16396, sackOK, TS val 255474104 ecr 0, nop, wscale 3], length 0 (1)
14:12: 45.104701 IP localhost.9502 > localhost.39870: Flags [S.], seq 1721825043, ack 2927179379, win 32768, options [mss 16396, sackOK, TS val 255474104 ecr 255474104, nop, wscale 3] , length 0 (2)
14:12:45.104711 IP localhost.39870 > localhost.9502: Flags [.], ack 1, win 4099, options [nop,nop,TS val 255474104 ecr 255474104], length 0 (3)


14: 13:01.415407 IP localhost.39870 > localhost.9502: Flags [P.], seq 1:8, ack 1, win 4099, options [nop,nop,TS val 255478182 ecr 255474104], length 7
14:13: 01.415432 IP localhost.9502 > localhost.39870: Flags [.], ack 8, win 4096, options [nop,nop,TS val 255478182 ecr 255478182], length 0
14:13:01.415747 IP localhost.9502 > localhost .39870: Flags [P.], seq 1:19, ack 8, win 4096, options [nop,nop,TS val 255478182 ecr 255478182], length 18
14:13:01.415757 IP localhost.39870 > localhost.9502 : Flags [.], ACK 19, Win 4097, Options [NOP, NOP, TS Val 255478182 ECR 255478182], Length 0


114: 12: 45.104687 time with accurate to delicate

Localhost. 39870 > localhost.9502 represents the flow of communication, 39870 is the client, 9502 is the server

[S] means this is a SYN request

[S.] means this is a SYN+ACK confirmation package:

[.] means this is an ACT confirmation packet, (client)SYN->(server)SYN->(client)ACT is the 3-way handshake process

[P] means this is a data push, which can be from the server It can be pushed from the client to the client, or from the client to the server.

[F] means that this is a FIN packet, which is a connection closing operation. Client/server may initiate it.

[R] means that this is an RST package. , has the same effect as the F package, but RST indicates that when the connection is closed, there is still data that has not been processed. It can be understood as forcibly cutting off the connection

win 4099 refers to the sliding window size

length 18 refers to the size of the data packet


We see that (1) (2) (3) the three steps are to establish tcp:

First handshake:

14:12:45.104687 IP localhost.39870 > localhost.9502: Flags [S], seq 2927179378

Client IP localhost.39870 (the client's port is usually automatically assigned) to the server localhost .9502 Send syn package (syn=j) to the server》

syn package (syn=j): syn seq= 2927179378 (j=2927179378)

Second handshake:

14:12:45.104701 IP localhost.9502 > localhost.39870: Flags [S.], seq 1721825043, ack 2927179379,

Receive request and confirm: The server receives the syn packet and must confirm the client's SYN (ack=j+1), and at the same time It also sends a SYN packet (syn=k), that is, SYN+ACK packet:
At this time, the server host’s own SYN: seq: y= syn seq 1721825043.
ACK is j+1 = (ack=j+1) =ack 2927179379


The third handshake:

14:12:45.104711 IP localhost.39870 > localhost.9502: Flags [.], ack 1.

The client receives the SYN+ACK packet from the server and sends an acknowledgment packet ACK (ack=k+1) to the server

After the client and server enter the ESTABLISHED state, they can exchange communication data. This time has nothing to do with the accept interface. Even if there is no accepte, the three-way handshake is completed.

連接出現(xiàn)連接不上的問題,一般是網(wǎng)路出現(xiàn)問題或者網(wǎng)卡超負(fù)荷或者是連接數(shù)已經(jīng)滿啦。

紫色背景的部分:

IP localhost.39870 > localhost.9502: Flags [P.], seq 1:8, ack 1, win 4099, options [nop,nop,TS val 255478182 ecr 255474104], length 7

客戶端向服務(wù)器發(fā)送長(zhǎng)度為7個(gè)字節(jié)的數(shù)據(jù),


IP localhost.9502 > localhost.39870: Flags [.], ack 8, win 4096, options [nop,nop,TS val 255478182 ecr 255478182], length 0

服務(wù)器向客戶確認(rèn)已經(jīng)收到數(shù)據(jù)


?IP localhost.9502 > localhost.39870: Flags [P.], seq 1:19, ack 8, win 4096, options [nop,nop,TS val 255478182 ecr 255478182], length 18

然后服務(wù)器同時(shí)向客戶端寫入數(shù)據(jù)。


?IP localhost.39870 > localhost.9502: Flags [.], ack 19, win 4097, options [nop,nop,TS val 255478182 ecr 255478182], length 0

客戶端向服務(wù)器確認(rèn)已經(jīng)收到數(shù)據(jù)

這個(gè)就是tcp可靠的連接,每次通信都需要對(duì)方來確認(rèn)。


6. Detailed explanation of SOCKET programming in Linux

建立一個(gè)連接需要三次握手,而終止一個(gè)連接要經(jīng)過四次握手,這是由TCP的半關(guān)閉(half-close)造成的,如圖:

Detailed explanation of SOCKET programming in Linux

由于TCP連接是全雙工的,因此每個(gè)方向都必須單獨(dú)進(jìn)行關(guān)閉。這個(gè)原則是當(dāng)一方完成它的數(shù)據(jù)發(fā)送任務(wù)后就能發(fā)送一個(gè)FIN來終止這個(gè)方向的連接。收到一個(gè)?FIN只意味著這一方向上沒有數(shù)據(jù)流動(dòng),一個(gè)TCP連接在收到一個(gè)FIN后仍能發(fā)送數(shù)據(jù)。首先進(jìn)行關(guān)閉的一方將執(zhí)行主動(dòng)關(guān)閉,而另一方執(zhí)行被動(dòng)關(guān)閉。

(1)客戶端A發(fā)送一個(gè)FIN,用來關(guān)閉客戶A到服務(wù)器B的數(shù)據(jù)傳送(報(bào)文段4)。

(2)服務(wù)器B收到這個(gè)FIN,它發(fā)回一個(gè)ACK,確認(rèn)序號(hào)為收到的序號(hào)加1(報(bào)文段5)。和SYN一樣,一個(gè)FIN將占用一個(gè)序號(hào)。

(3)服務(wù)器B關(guān)閉與客戶端A的連接,發(fā)送一個(gè)FIN給客戶端A(報(bào)文段6)。

(4)客戶端A發(fā)回ACK報(bào)文確認(rèn),并將確認(rèn)序號(hào)設(shè)置為收到序號(hào)加1(報(bào)文段7)。

Detailed explanation of SOCKET programming in Linux如圖:

Detailed explanation of SOCKET programming in Linux

過程如下:

某個(gè)應(yīng)用進(jìn)程首先調(diào)用close主動(dòng)關(guān)閉連接,這時(shí)TCP發(fā)送一個(gè)FIN M;

另一端接收到FIN M之后,執(zhí)行被動(dòng)關(guān)閉,對(duì)這個(gè)FIN進(jìn)行確認(rèn)。它的接收也作為文件結(jié)束符傳遞給應(yīng)用進(jìn)程,因?yàn)镕IN的接收意味著應(yīng)用進(jìn)程在相應(yīng)的連接上再也接收不到額外數(shù)據(jù);

一段時(shí)間之后,接收到文件結(jié)束符的應(yīng)用進(jìn)程調(diào)用close關(guān)閉它的socket。這導(dǎo)致它的TCP也發(fā)送一個(gè)FIN N;

接收到這個(gè)FIN的源發(fā)送端TCP對(duì)它進(jìn)行確認(rèn)。

這樣每個(gè)方向上都有一個(gè)FIN和ACK。

1.為什么建立連接協(xié)議是三次握手,而關(guān)閉連接卻是四次握手呢?

這是因?yàn)榉?wù)端的LISTEN狀態(tài)下的SOCKET當(dāng)收到SYN報(bào)文的建連請(qǐng)求后,它可以把ACK和SYN(ACK起應(yīng)答作用,而SYN起同步作用)放在一個(gè)報(bào)文里來發(fā)送。但關(guān)閉連接時(shí),當(dāng)收到對(duì)方的FIN報(bào)文通知時(shí),它僅僅表示對(duì)方?jīng)]有數(shù)據(jù)發(fā)送給你了;但未必你所有的數(shù)據(jù)都全部發(fā)送給對(duì)方了,所以你可以未必會(huì)馬上會(huì)關(guān)閉SOCKET,也即你可能還需要發(fā)送一些數(shù)據(jù)給對(duì)方之后,再發(fā)送FIN報(bào)文給對(duì)方來表示你同意現(xiàn)在可以關(guān)閉連接了,所以它這里的ACK報(bào)文和FIN報(bào)文多數(shù)情況下都是分開發(fā)送的。


2.為什么TIME_WAIT狀態(tài)還需要等2MSL后才能返回到CLOSED狀態(tài)?

這是因?yàn)殡m然雙方都同意關(guān)閉連接了,而且握手的4個(gè)報(bào)文也都協(xié)調(diào)和發(fā)送完畢,按理可以直接回到CLOSED狀態(tài)(就好比從SYN_SEND狀態(tài)到ESTABLISH狀態(tài)那樣);但是因?yàn)槲覀儽仨氁傧刖W(wǎng)絡(luò)是不可靠的,你無法保證你最后發(fā)送的ACK報(bào)文會(huì)一定被對(duì)方收到,因此對(duì)方處于LAST_ACK狀態(tài)下的SOCKET可能會(huì)因?yàn)槌瑫r(shí)未收到ACK報(bào)文,而重發(fā)FIN報(bào)文,所以這個(gè)TIME_WAIT狀態(tài)的作用就是用來重發(fā)可能丟失的ACK報(bào)文。


7. Socket編程實(shí)例

服務(wù)器端:一直監(jiān)聽本機(jī)的8000號(hào)端口,如果收到連接請(qǐng)求,將接收請(qǐng)求并接收客戶端發(fā)來的消息,并向客戶端返回消息。

/* File Name: server.c */  
#include<stdio.h>  
#include<stdlib.h>  
#include<string.h>  
#include<errno.h>  
#include<sys/types.h>  
#include<sys/socket.h>  
#include<netinet/in.h>  
#define DEFAULT_PORT 8000  
#define MAXLINE 4096  
int main(int argc, char** argv)  
{  
    int    socket_fd, connect_fd;  
    struct sockaddr_in     servaddr;  
    char    buff[4096];  
    int     n;  
    //初始化Socket  
    if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){  
    printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);  
    exit(0);  
    }  
    //初始化  
    memset(&servaddr, 0, sizeof(servaddr));  
    servaddr.sin_family = AF_INET;  
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址設(shè)置成INADDR_ANY,讓系統(tǒng)自動(dòng)獲取本機(jī)的IP地址。  
    servaddr.sin_port = htons(DEFAULT_PORT);//設(shè)置的端口為DEFAULT_PORT  
  
    //將本地地址綁定到所創(chuàng)建的套接字上  
    if( bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){  
    printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);  
    exit(0);  
    }  
    //開始監(jiān)聽是否有客戶端連接  
    if( listen(socket_fd, 10) == -1){  
    printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);  
    exit(0);  
    }  
    printf("======waiting for client&#39;s request======\n");  
    while(1){  
//阻塞直到有客戶端連接,不然多浪費(fèi)CPU資源。  
        if( (connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1){  
        printf("accept socket error: %s(errno: %d)",strerror(errno),errno);  
        continue;  
    }  
//接受客戶端傳過來的數(shù)據(jù)  
    n = recv(connect_fd, buff, MAXLINE, 0);  
//向客戶端發(fā)送回應(yīng)數(shù)據(jù)  
    if(!fork()){ /*紫禁城*/  
        if(send(connect_fd, "Hello,you are connected!\n", 26,0) == -1)  
        perror("send error");  
        close(connect_fd);  
        exit(0);  
    }  
    buff[n] = &#39;\0&#39;;  
    printf("recv msg from client: %s\n", buff);  
    close(connect_fd);  
    }  
    close(socket_fd);  
}

客戶端:

/* File Name: client.c */  
  
#include<stdio.h>  
#include<stdlib.h>  
#include<string.h>  
#include<errno.h>  
#include<sys/types.h>  
#include<sys/socket.h>  
#include<netinet/in.h>  
  
#define MAXLINE 4096  
  
  
int main(int argc, char** argv)  
{  
    int    sockfd, n,rec_len;  
    char    recvline[4096], sendline[4096];  
    char    buf[MAXLINE];  
    struct sockaddr_in    servaddr;  
  
  
    if( argc != 2){  
    printf("usage: ./client <ipaddress>\n");  
    exit(0);  
    }  
  
  
    if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){  
    printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);  
    exit(0);  
    }  
  
  
    memset(&servaddr, 0, sizeof(servaddr));  
    servaddr.sin_family = AF_INET;  
    servaddr.sin_port = htons(8000);  
    if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){  
    printf("inet_pton error for %s\n",argv[1]);  
    exit(0);  
    }  
  
  
    if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){  
    printf("connect error: %s(errno: %d)\n",strerror(errno),errno);  
    exit(0);  
    }  
  
  
    printf("send msg to server: \n");  
    fgets(sendline, 4096, stdin);  
    if( send(sockfd, sendline, strlen(sendline), 0) < 0)  
    {  
    printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);  
    exit(0);  
    }  
    if((rec_len = recv(sockfd, buf, MAXLINE,0)) == -1) {  
       perror("recv error");  
       exit(1);  
    }  
    buf[rec_len]  = &#39;\0&#39;;  
    printf("Received : %s ",buf);  
    close(sockfd);  
    exit(0);  
}

inet_pton 是Linux下IP地址轉(zhuǎn)換函數(shù),可以在將IP地址在“點(diǎn)分十進(jìn)制”和“整數(shù)”之間轉(zhuǎn)換 ,是inet_addr的擴(kuò)展。

int inet_pton(int af, const char *src, void *dst);//轉(zhuǎn)換字符串到網(wǎng)絡(luò)地址:

第一個(gè)參數(shù)af是地址族,轉(zhuǎn)換后存在dst中
? ? af = AF_INET:src為指向字符型的地址,即ASCII的地址的首地址(ddd.ddd.ddd.ddd格式的),函數(shù)將該地址轉(zhuǎn)換為in_addr的結(jié)構(gòu)體,并復(fù)制在*dst中
  af =AF_INET6:src為指向IPV6的地址,函數(shù)將該地址轉(zhuǎn)換為in6_addr的結(jié)構(gòu)體,并復(fù)制在*dst中
如果函數(shù)出錯(cuò)將返回一個(gè)負(fù)值,并將errno設(shè)置為EAFNOSUPPORT,如果參數(shù)af指定的地址族和src格式不對(duì),函數(shù)將返回0。


測(cè)試:

編譯server.c

gcc -o server server.c

啟動(dòng)進(jìn)程:

./server

顯示結(jié)果:

======waiting for client's request======

并等待客戶端連接。

編譯 client.c

gcc -o client server.c

客戶端去連接server:

./client 127.0.0.1?

等待輸入消息

Detailed explanation of SOCKET programming in Linux

發(fā)送一條消息,輸入:c++

Detailed explanation of SOCKET programming in Linux

此時(shí)服務(wù)器端看到:

Detailed explanation of SOCKET programming in Linux

客戶端收到消息:

Detailed explanation of SOCKET programming in Linux

其實(shí)可以不用client,可以使用telnet來測(cè)試:

telnet 127.0.0.1 8000

Detailed explanation of SOCKET programming in Linux

注意:

在ubuntu 編譯源代碼的時(shí)候,頭文件types.h可能找不到。
使用dpkg -L libc6-dev | grep types.h 查看。
如果沒有,可以使用
apt-get install libc6-dev安裝。
如果有了,但不在/usr/include/sys/目錄下,手動(dòng)把這個(gè)文件添加到這個(gè)目錄下就可以了。


Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undress AI Tool

Undress AI Tool

Undress images for free

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Hot Topics

PHP Tutorial
1502
276