C/C++ server and client interaction notes

C/C++ server development

Network and Communication Socket

There are three elements of Socket communication: the destination address of the communication, the port number used (http 80 / smtp 25), and the transmission protocol used (TCP, UDP).

nslookup xxYou can query the IP address of the xx website.

The Socket communication model

telnet ipxx communicates between hosts.

A simple server and client communication program, server-side code:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ctype.h>
#include <arpa/inet.h>

#define SERVER_PORT 666

int main(void)
{
    
    

    int sock;

    struct sockaddr_in server_addr;

    sock = socket(AF_INET, SOCK_STREAM, 0);

    // printf("wait \n");

    bzero(&server_addr, sizeof(server_addr));

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERVER_PORT);

    bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));

    listen(sock, 128);
    printf("wait client connect\n");
    

    int done = 1;
    while (done)
    {
    
    
        struct sockaddr_in client;
        int client_sock, len, i;
        char client_ip[64];
        char buf[256];

        socklen_t client_addr_len;
        client_addr_len = sizeof(client);
        client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);

        printf("client ip: %s \t port is : %d \n", inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)), ntohs(client.sin_port));

        len = read(client_sock, buf, sizeof(buf) - 1);
        buf[len] = '\0';
        printf("receive[%d]:%s\n", len, buf);

        len = write(client_sock, buf, len);
        printf("finish. len:%d\n", len);
        close(client_sock);
    }
    close(sock);

    return 0;
}

Client code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define SERVER_PORT 666
#define SERVER_IP  "127.0.0.1"

int main(int argc, char *argv[]){
    
    

    int sockfd;
    char *message;
    struct sockaddr_in servaddr;
    int n;
    char buf[64];

    if(argc != 2){
    
    
        fputs("Usage: ./echo_client message \n", stderr);
        exit(1);
    }

    message = argv[1];

    printf("message: %s\n", message);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
	//重置结构体的内存空间
    memset(&servaddr, '\0', sizeof(struct sockaddr_in));

    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr);
    servaddr.sin_port = htons(SERVER_PORT);

    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    write(sockfd, message, strlen(message));

    n = read(sockfd, buf, sizeof(buf)-1);

    if(n>0){
    
    
        buf[n]='\0';
        printf("receive: %s\n", buf);
    }else {
    
    
        perror("error!!!");
    }

    printf("finished.\n");
    close(sockfd);

    return 0;
}

Socket concept

The Chinese meaning of socket (socket) is socket, and socket is generally represented by an integer. In Linux, it represents a special file type for network communication between processes x. Essentially, a file is formed by the kernel with the help of a buffer. A socket can be referenced using a file descriptor . The purpose of packaging it into a file in the Linux system is to unify the interface, so that the operations of reading and writing sockets and reading and writing files are consistent. The difference is that files are mainly used for reading and writing of local persistent data, while sockets are mostly used for data transfer between network processes.

In the TCP/IP protocol, the IP address-TCP or UDP port number uniquely identifies a process in network communication. IP address + port number corresponds to a socket. The two processes that establish a connection are each identified by a socket, and the socket pair formed by these two sockets uniquely identifies a connection. Therefore, Socket can be used to describe the one-to-one relationship of network connections.


In network communication, sockets must appear in pairs . One end's send buffer corresponds to the other end's receive buffer. Use the same file descriptor for send and receive buffers.


The communication between the server and the client is full-duplex, they can read and write to each other, and interact in a synchronous and asynchronous manner.

Waved four times to end the communication between client and server.

network byte order

Big endian - low address high byte, high address low byte .
Little endian - low address low byte, high address high byte .

The multi-byte data in the memory is relative to the memory address, the multi-byte data in the disk file is relative to the offset address in the file, and the network data stream is divided into big endian and little endian. The sending host usually sends the data in the sending buffer in the order of memory addresses from low to high, and the receiving host saves the bytes received from the network in the receiving buffer at one time, also in the order of memory addresses from low to high save. Therefore, the address of the network data flow should be stipulated in this way: the data sent first is the low address, and the data sent later is the high address.

The TCP/IP protocol stipulates that network data streams should adopt big-endian byte order, that is, low address and high byte .

The 32-bit IP address should also consider the issue of network byte order and host byte order. In C/C++, the following library functions are used to convert the network byte order and the host byte order.

//头文件,库函数
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

hRepresents host, nrepresents network, lrepresents a 32-bit long integer, and srepresents a 16-bit short integer.

If the host is little-endian, these functions convert the parameters into corresponding big-endian and return them. If the host is big-endian, these functions do not convert and return the parameters unchanged .

Detailed explanation of SocketAddr

sockaddrMany network programming functions were born earlier than the IPv4 protocol. At that time , structures were used . In order to be forward compatible, they have now sockaddrdegenerated into (void *)a function, passing an address to the function. As for whether this function is sockaddr_inanother, it is determined by the address family. Then the function internally forces the type to be converted to the required address type.

SocketAddress structure diagram
struct sockaddr {
    
    
	sa_family_t sa_family;/* address family, AF_xxx AF_INET(IPV4) AF_INET(IPV6)*/
	char sa_data[14];     /* 14 bytes of protocol address */
};

 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. */
struct in_addr {
    
    
    uint32_t       s_addr;     /* address in network byte order */
};

The IPv4 address format is defined in netinet/in.h, and the IPv4 address is sockaddr_inrepresented by a structure, including a 16-bit port number and a 32-bit IP address, but the implementation of the sock API was earlier than ANSI Cthe standardization, and there was no type at that time , so the parameters of void *these images bind and functions are all used The type indicates that the type must be converted before passing the parameter, for example:acceptstruct sockaddr *

struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));/* initialize servaddr */

IP address conversion

#include <arpa/inet.h>
//将字符串的IP转化为网络的整型IP
int inet_pton(int af, const char *src, void *dst);
//将网络字节序的IP转化为字符串的IP
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

afThe value can be AF_INETand AF_INET6, that is, corresponding to ipv4 and ipv6, it supports IPv4 and IPv6
assuming host address bits 2.3.4.5, where 2it means the low bit and 5the high bit, then the result of the big endian byte order is 5040302(use inet_pton(AF_INET,"2.3.4.5",&s_add)for conversion), and the little endian byte order is 2030405(use ntohl(s_addr)to convert).

ipconfig /allView the network address of the host.

Socket programming

socket function

//头文件
#include<sys/types.h>
#include<sys/socket.h>

int socket(int doamin,int type,int protocol);

domain:
	AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPV4的地址。
	AF_INET6 和AF_INET类似,不过是用来IPV6的地址。
	AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台主机及同时使用的的协议。
type:
	SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完成的基于字节流的连接。这是一个使用最多的socket类型,用于TCP进行传输的。
	SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行连接。
	SOCK_SEQPACKET 该协议是双线路的,可靠的链接,发送固定长度的数据包进行传输。必须把这个包完整接受才能进行读取。
	SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
	SOCK_RDM 这个类型是很少使用的,在大部分操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序。
protocol:0表示使用默认协议
return
	成功:返回只想新创建的socket的文件描述符。失败:返回-1,并设置errno。

socket()Open a network communication port, if successful, open()return a file descriptor, the application can read/writesend and receive data on the network like reading and writing files, and socket()return -1 if the call fails.

bind function

The network address and port number that the server program monitors are usually fixed, and the client program can initiate a connection to the server after learning the address and port number of the server program, so the server needs to call for binding bind.

//头文件
#include<sys/type.h>
#include<sys/socket.h>

int bind(int sockfd,const struct sockaddr *addr,socklen_t addren);

sockfd:
	socket文件描述符
addr:
	购找出IP地址加端口号
addrlen:
	sizeof(addr)长度
return:
	成功返回0。失败返回-1,设置errno。

bind()The role of is to bind the parameters sockfdand addrtogether, so that sockfdthe file descriptor used for network communication listens addrto the described address and port number. struct sockaddr *It is a general pointer type. addrThe parameter can actually accept sockaddrstructures of various protocols, and their lengths are different, so the third parameter is required addrlento specify the length of the structure.

struct sockaddr_in servaddr;
//结构体清空很重要
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);

Listen function

A typical server program can serve multiple clients at the same time. When a client initiates a connection, the function called by the server accept()returns and accepts the connection. If there are a large number of U-segment initiated connections and the server has no time to process them, Sunway accepts the client The terminal is in this connection waiting state, and listen()the monk name sockfd is in the monitoring state. If it receives more connection requests, it will be ignored. If listen()it succeeds, it will return 0, and if it fails, it will return -1.

//头文件
#include<sys/types.h>
#include<sys/socket.h>

int listen(int sockfd,int backlog);

sockfd:
	socket文件描述符
backlog:
	在Linux系统中,它是指排队等待建立3次握手队列长度

View system defaultbacklog

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

accept function

//头文件
#include<sys/types.h>
#include<sys/socket.h>

int accept(int sockfd,struct aockaddr *addr,socklen_t *addrlen);

sockdf:
	socket文件描述符
addr:
	传出的参数,返回链接客户端地址信息,含IP地址和端口号
addrlen:
	传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接受到地址结构体的大小
return
	返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,并设置errno

After the three-way handshake, the server calls accept()to accept the connection. If accept()there is no connection request from the client when the server calls, it will block and wait until a client connects. addrIs an outgoing parameter, accept()and returns the address and port number of the outgoing client.

Example of server-side code structure:

while (1) {
    
    
	cliaddr_len = sizeof(cliaddr);
	//如果没有客户端连接就会一直堵塞在这个代码上,不会往下进行执行
	connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
	n = read(connfd, buf, MAXLINE);
	.......
	close(connfd);
}

The whole structure is an whileendless loop, each loop handles a client connection, since cliaddr_lenit is an incoming and outgoing parameter, accept()the initial value should be reassigned before each call. accept()The parameter listenfsis the previously monitored file descriptor, and accept()the return value of is another file descriptor connfd, and then it communicates with the client through or connfd, and finally closes connfdthe disconnection without closing it listenfd, and returns to the beginning of the loop again listenfdto still be used as acceptparameter. accept()Returns a file descriptor on success, or -1 on error.

connect function

//头文件
#include<sys/types.h>
#include<sys/socket.h>

int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
sockdf:
	socket文件描述符
addr:
	传入参数,指定服务器端地址信息,含IP地址和端口号
addrlen:
	传入参数,指定服务器段地址信息,含IP地址和端口信息
return
	成功返回0,失败返回-1,并设置errno

The client needs to call to connect()connect to the server, connectwhich bindis the same as the parameter of , the difference is that bindthe parameter of is its own address, while connectthe parameter of is the address of the other party.

error handling function

The system call cannot guarantee the success of each execution, and the program failure information should be obtained as soon as possible.

//头文件
#include<errno.h>
#include<string.h>

char *strerror(int errnum)
errnum:
	传入参数,错误编号的值,一般去errno的值
return
	错误原因
	
#include<stdio.h>
#include<errno.h>
void perror(const char *s);
s:
	传入参数,自定义描述
return
向标准出错stdeer输出出错原因(控制台打印)。

Guess you like

Origin blog.csdn.net/qq_45041871/article/details/131513078