Overview #
-
First, the network. When we see this word, we immediately think of IP addresses and port numbers. So, what are the respective roles of IP addresses and ports?
- An IP address is like an ID card, uniquely identifying a computer. Each computer has only one IP address.
- Ports provide an access channel. Servers generally identify specific services through well-known port numbers. For example, for every TCP/IP implementation, the TCP port number for an FTP server is 21, the TCP port number for each Telnet server is 23, and the UDP port number for each TFTP (Trivial File Transfer Protocol) server is 69.
-
Network socket transmission protocols are TCP and UDP. This learning session will utilize the TCP protocol for data transmission.
Differences between TCP and UDP:
- TCP is connection-oriented (like making a phone call where you need to dial and establish a connection first); UDP is connectionless, meaning there is no need to establish a connection before sending data.
- TCP provides reliable service. That is, data transmitted through a TCP connection is error-free, not lost, not duplicated, and arrives in order; UDP makes a best effort to deliver, meaning it does not guarantee reliable delivery.
- TCP is byte-stream oriented; in essence, TCP treats data as a continuous, unstructured stream of bytes; UDP is message-oriented. UDP does not have congestion control, so network congestion will not reduce the sending rate of the source host (very useful for real-time applications such as IP telephony and real-time video conferencing).
- Each TCP connection can only be point-to-point; UDP supports one-to-one, one-to-many, many-to-one, and many-to-many interactive communication.
- The TCP header has an overhead of 20 bytes; the UDP header has a smaller overhead of only 8 bytes.
- The logical communication channel of TCP is a full-duplex reliable channel, while UDP is an unreliable channel.
Byte Order #
Byte order refers to the order in which multi-byte data is stored in computer memory or transmitted over a network.
Common Orders:
-
Little endian: Stores the least significant byte at the starting address.
-
Big endian: Stores the most significant byte at the starting address.
Example: If we write
0x1234abcd
to memory starting at address0x0000
, the result will be:Address Little endian Big endian 0x0000 0xcd
0x12
0x0001 0xab
0x34
0x0002 0x34
0xab
0x0003 0x12
0xcd
Network byte order = Big endian, X86 series CPUs use little endian.
Socket Development #
Socket development is similar to file operations. First, create a socket to get a file descriptor, then bind, listen, read/write, and close.
Development Steps:
-
Socket Creation
Function Prototype:
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> // Returns a file descriptor on success, -1 on error int socket(int domain, int type, int protocol);
Parameter Description:
-
domain
: Specifies the protocol family to be used, usuallyAF_INET
, which represents the Internet Protocol family (TCP/IP protocol family);AF_INET
IPv4 Internet domainAF_INET6
IPv6 Internet domainAF_UNIX
Unix domainAF_ROUTE
Routing socketAF_KEY
Key socketAF_UNSPEC
Unspecified
-
type
: Specifies the type of socket:SOCK_STREAM
: Provides reliable, connection-oriented communication streams; it uses the TCP protocol, thus ensuring the correctness and order of data transmission.SOCK_DGRAM
: Defines a connectionless service where data is transmitted in independent packets, which are unordered and do not guarantee reliability or error-free delivery. It uses the UDP protocol.SOCK_RAW
: Allows programs to use lower-level protocols. Raw sockets allow direct access to lower layers such as IP or ICMP, which is powerful but less convenient to use and mainly used for the development of some protocols.
-
protocol
: Usually set to0
.0
Selects the default protocol corresponding to thetype
.IPPROTO_TCP
TCP transmission protocolIPPROTO_UDP
UDP transmission protocolIPPROTO_SCTP
SCTP transmission protocolIPPROTO_TIPC
TIPC transmission protocol
-
-
Bind Function:
bind()
Functionality: Used to bind an IP address and port number to a socket file descriptor (
sockfd
).Function Prototype:
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Parameter Description:
sockfd
: A socket file descriptor.addr
: A pointer to astruct sockaddr
type, containing IP address and port number information, pointing to the protocol address structure to be bound tosockfd
. This address structure varies depending on the address protocol family used when creating the socket.
struct sockaddr { sa_family_t sa_family; // Protocol family char sa_data[14]; // IP address + port } ;
Equivalent Replacement:
#include<linux/in.h> struct sockaddr_in { __kernel_sa_family_t sin_family; // Protocol family __be16 sin_port; // Port number struct in_addr sin_addr; // IP address structure unsigned char sin_zero[8]; // Padding, no actual meaning, just for byte alignment // with the sockaddr structure in memory, so that // the two can be mutually converted. };
-
Listen Function:
listen()
Function Prototype:
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> // Returns 0 on successful execution, -1 otherwise int listen(int sockfd, int backlog);
Parameter Description:
sockfd
: The server-side socket file descriptor returned by thesocket
system call.backlog
: Specifies the maximum number of pending connection requests allowed in the queue, i.e., the maximum number of connections. Functionality: Sets the maximum number of connections that can be handled. Initially, only the listen mode of the socket is set. Thelisten
function is only used on the server side. The server process does not know which client to connect to, so it does not actively initiate connections but continuously listens for connection requests from other client processes and then responds to these connection requests. A server process can handle connections from multiple client processes simultaneously.
-
Accept Function:
accept()
Function Prototype:
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
Parameter Description:
sockfd
: The server-side socket file descriptor returned by thesocket
system call.addr
: Used to return the protocol address of the connected peer (client).addrlen
: The length of the client address.
Return Value:
This function returns a new socket file descriptor for the connected peer (client). The kernel creates a connected socket (representing the completion of the TCP three-way handshake) for each client connection accepted by the server process. When the server finishes serving a given client, the corresponding connected socket is closed.
-
Address Conversion API
Function Prototype:
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> // Functionality: Converts a string in the form "192.168.1.123" to a network-recognizable format and stores it in inp. int inet_aton(const char *cp, struct in_addr *inp); // Functionality: Converts a network-formatted IP address to a string. char *inet_ntoa(struct in_addr in);
-
Byte Order Conversion API
Function Prototype:
#include <arpa/inet.h> // Returns the value in network byte order. uint32_t htonl(uint32_t hostlong); // Returns the value in network byte order. uint16_t htons(uint16_t hostshort); // Returns the value in host byte order. uint32_t ntohl(uint32_t netlong); // Returns the value in host byte order. uint16_t ntohs(uint16_t netshort);
Function Description:
h
represents hostn
represents nets
represents short (two bytes)l
represents long (four bytes)
The above four functions can be used to convert between host byte order and network byte order. Sometimes
INADDR_ANY
can be used, which tells the operating system to obtain the address itself. -
Common Data Sending and Receiving Functions
The
read()
andwrite()
functions are used for reading and writing bytes during socket communication. They differ slightly from the read functions in file I/O because the number of bytes they input or output may be less than requested. You can test this; the number of bytes input or output is the number of valid bytes.#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);
The second set of read and write functions,
send()
andrecv()
, can also be used.-
send()
: Sends dataFunction Prototype:
#include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags);
Parameter Description:
sockfd
: The socket file descriptor of the newly connected peer (client), which is the return value of theaccept
function.buf
: Stores the data to be sent.len
: The length of the data to be sent.flags
: Control options, usually set to0
.
Return Value: Returns the length of the data sent on successful execution,
-1
otherwise. -
recv()
function: Receives dataFunction Prototype:
#include <sys/types.h> #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags);
Parameter Description:
sockfd
: The socket file descriptor of the newly connected peer (client), which is the return value of theaccept
function.buf
: Stores the received data.len
: The length of the data to be received.flags
: Control options, usually set to0
.
-
-
Client Connection Function:
connect()
Functionality: This function is used by the client to establish a connection with the server.
Function Prototype:
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> // Returns 0 on successful execution, -1 otherwise int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Parameter Description:
sockfd
: The socket file descriptor of the target server.addr
: A pointer to the address structure containing the server’s IP address and port number.addrlen
: The length of theaddr
address, usually set tosizeof(struct sockaddr)
.
Programming Implementation of Server-Client Connection Establishment #
server.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
int s_fd;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset((void *)&s_addr,0,sizeof(struct sockaddr_in));
memset((void *)&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
s_fd=socket(AF_INET,SOCK_STREAM,0);
printf("s_fd=%d\n",s_fd);
if(s_fd==-1)
{
perror("socket");
exit(0);
}
//2.bind
s_addr.sin_family=AF_INET;
s_addr.sin_port =htons(8888);
inet_aton("your_local_ip_address",&s_addr.sin_addr);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
//4.accept
int cnt =sizeof(struct sockaddr_in);
int c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&cnt);
if(c_fd==-1)
{
perror("accept");
exit(0);
}else{
printf("get success:%s\n",inet_ntoa(c_addr.sin_addr));
}
//5.read
char readbuff[128]={0};
int nread=read(c_fd,readbuff,128);
if(nread==-1)
{
perror("read");
}
else{
printf("msg data from clinet:%d,%s\n",nread,readbuff);
memset(readbuff,0,128);
}
//6.write
write(c_fd,"I get your connect",strlen("I get your connect"));
//用read()检测客户端是否退出
nread=read(c_fd,readbuff,128);
if(nread==0)
printf("connect end\n");
else
printf("nread:%d\n",nread);
//关闭
close(s_fd);
close(c_fd);
return 0;
}
client.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
int c_fd;
struct sockaddr_in c_addr;
memset((void *)&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd==-1)
{
perror("socket");
exit(0);
}
c_addr.sin_family=AF_INET;
c_addr.sin_port =htons(8888);//端口号一般设置为5000~9000
inet_aton("your_local_ip_address",&c_addr.sin_addr);
//connect
int ct=connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in));
if(ct==-1)
{
perror("connect");
exit(0);
}
write(c_fd,"I get connect",strlen("I get connect"));
char readbuff[128];
int nread=read(c_fd,readbuff,128);
if(nread==-1)
{
perror("read");
}
else{
printf("read data:%s\n",readbuff);
}
close(c_fd);
return 0;
}