(Network programming notes): SOCKET programming

table of Contents

SOCKET programming

Network byte order

IP address conversion function

An important structure used in socket programming: struct sockaddr

Introduction to the main API functions of socket programming

Socket API function steps to write server and client programs

Server development process

Client development process

Server program example

Client program example

SOCKET programming

  • Traditional inter-process communication is carried out with the help of the IPC mechanism provided by the kernel, but it can only be limited to local machine communication. If you want to communicate across machines, you must use network communication. (Essentially by means of the kernel-kernel provides a socket pseudo-file mechanism to achieve communication-actually using file descriptors ), this requires the use of the socket API function library provided by the kernel to the user
  • Since the socket pseudo file is mentioned, you can use the function read write related to the file descriptor
  • Using socket will create a socket pair
  • As shown in the figure below, one file descriptor operates two buffers , which is different from a pipe, which is a kernel buffer with two file descriptors.

Network byte order

  • The concept of big endian and little endian
    • Big endian: low-order address stores high-order data, high-order address stores low-order data
    • Little endian: low-order addresses store low-order data, high-order addresses store high-order data
  • Use occasions of big-endian and little-endian:
    • Big-endian and little-endian only have two or more data types in length, such as int short. There is no restriction on single byte. In the network, it is often necessary to consider IP and port for big-endian and little-endian.
  • Big-endian is used for network transmission. If the machine uses little-endian, it needs to be converted between large and small
  • The following four functions are the functions for performing large and small endian conversion:
#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);
  • The h in the function name means host, n means network, s means short, l means long
  • The above several functions will not be converted if they do not need to be converted inside the function .

IP address conversion function

  • p-> represents the string form of dotted decimal notation
  • to->to
  • n-> means network
int inet_pton(int af, const char *src, void *dst);
  • Function description : Convert the dotted decimal IP in the form of a string to the network IP in big-endian mode (shape 4 bytes)
  • Parameter Description:
    • by: AF_INET
    • src: dotted decimal IP address in string form
    • dst: store the address of the converted variable
  • For example: inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);    
    • It can also be calculated manually: such as 192.168.232.145, first convert the 4 positive numbers into hexadecimal numbers, 
    • 192--->0xC0  168--->0xA8   232--->0xE8   145--->0x91
    • Finally, it is stored in big-endian byte order: 0x91E8A8C0, this is the 4-byte integer value.
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
  • Function description: Convert network IP to dotted decimal IP in string form
  • Parameter Description:
    • by: AF_INET
    • src: the shaped IP address of the network
    • dst: converted IP address, generally a string array
    • size: length of dst
  • return value: 
    • Success-return a pointer to dst
    • Failure-return NULL, and set errno
  • For example: the IP address is 010aa8c0, converted to dotted decimal format:
    • 01---->1    0a---->10   a8---->168   c0---->192
    • Since the IP address in the slave network is in high-end mode, it should be converted to dotted decimal: 192.168.10.1

An important structure used in socket programming: struct sockaddr

  • struct sockaddr structure description:
struct sockaddr {
        sa_family_t sa_family;
        char     sa_data[14];
}
  • struct sockaddr_in structure:
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 */
};	 //网络字节序IP--大端模式
  • You can view related instructions through man 7 ip

Introduction to the main API functions of socket programming

int socket(int domain, int type, int protocol);
  • Function description : Create socket
  • Parameter Description:
    • domain: protocol version
      • AF_INET IPV4
      • AF_INET6 IPV6
      • AF_UNIX AF_LOCAL local socket use
    • type: protocol type
      • SOCK_STREAM streaming, the default protocol used is TCP protocol
      • SOCK_DGRAM report format, UDP protocol is used by default
    • protocal: 
      • Generally fill in 0, which means to use the default protocol of the corresponding type.
  • return value: 
    • Success: Return a file descriptor greater than 0
    • Failure: return -1, and set errno
  • When the socket function is called, a file descriptor is returned, the kernel will provide the read and write buffers corresponding to the file descriptor, and there are two queues, namely the request connection queue and the connected queue

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • Function description: bind the socket file descriptor with IP and PORT
  • Parameter Description:
    • socket: the file descriptor returned by calling the socket function
    • addr : the IP address and PORT of the local server, 
struct sockaddr_in serv;
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
//serv.sin_addr.s_addr = htonl(INADDR_ANY);
//INADDR_ANY: 表示使用本机任意有效的可用IP

inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
  • addrlen: the memory size of the addr variable 
  • return value: 
    • Success: returns 0
    • Failure: return -1, and set errno
int listen(int sockfd, int backlog);
  • Function description: change the socket from the main dynamic to the dynamic
  • Parameter Description:
    • sockfd: the file descriptor returned by calling the socket function
    • backlog: The maximum number of simultaneous requests (connections not yet established) 
  • return value:
    • Success: returns 0
    • Failure: return -1, and set errno                
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);    
  • Function description: Get a connection, if there is no connection currently, it will block waiting.
  • Function parameters:
    • sockfd: The file descriptor returned by calling the socket function
    • addr : outgoing parameters, save the client's address information
    • addrlen : incoming and outgoing parameters , the memory space occupied by addr variables
  • return value:
    • Success: Return a new file descriptor for communication with the client
    • Failure: Return -1, and set errno value.
  • The accept function is a blocking function, if there is no new connection request, it will always block.
  • Get a new connection from the connected queue and get a new file descriptor, which is used to communicate with the client . (The kernel will be responsible for getting the connection in the request queue to the connected queue)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • Function description: connect server
  • Function parameters:
    • sockfd: The file descriptor returned by calling the socket function
    • addr: server address information
    • addrlen: the memory size of the addr variable
  • return value:
    • Success: returns 0
    • Failure: return -1, and set errno value
  • Next, you can use the write and read functions to perform read and write operations.
  • In addition to using read/write functions, you can also use recv and send functions
  • Read data and send data:
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);    
  • Corresponding to the two function flags of recv and send, just fill in 0 directly .
  • Note : If the write buffer is full, write will also block. During a read operation, if there is no data in the read buffer, it will cause blocking.

Socket API function steps to write server and client programs

Server development process

  • Create a socket and return a file descriptor lfd---socket() - the file descriptor is used to monitor client connections
  • Bind lfd and IP PORT----bind()
  • Change lfd from active to passive monitoring----listen()
  • Accept a new connection and get a file descriptor cfd----accept() ---The file descriptor is used to communicate with the client
  • while(1)
    • {
    •     Receive data---read or recv
    •      Send data---write or send
    •  }
  • Close the file descriptor-close(lfd) close(cfd);

Client development process

  • Create a socket, return a file descriptor cfd---socket() - the file descriptor is used to communicate with the server
  • Connect to the server---connect() 
  • while(1)
    •  {
    •         //Send data---write or send
    •         //Receive data---read or recv
    • }
  • close(cfd)

Server program example

//服务端程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>

int main()
{
	//创建socket
	//int socket(int domain, int type, int protocol);
	int lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd<0)
	{
		perror("socket error");
		return -1;
	}
	
	//int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	//绑定
	struct sockaddr_in serv;
	bzero(&serv, sizeof(serv));
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	serv.sin_addr.s_addr = htonl(INADDR_ANY); //表示使用本地任意可用IP
	int ret = bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
	if(ret<0)
	{
		perror("bind error");	
		return -1;
	}

	//监听
	//int listen(int sockfd, int backlog);
	listen(lfd, 128);

	//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	struct sockaddr_in client;
	socklen_t len = sizeof(client);
	int cfd = accept(lfd, (struct sockaddr *)&client, &len);  //len是一个输入输出参数
	//const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
	
	//获取client端的IP和端口
	char sIP[16];
	memset(sIP, 0x00, sizeof(sIP));
	printf("client-->IP:[%s],PORT:[%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));
	printf("lfd==[%d], cfd==[%d]\n", lfd, cfd);

	int i = 0;
	int n = 0;
	char buf[1024];

	while(1)
	{
		//读数据
		memset(buf, 0x00, sizeof(buf));
		n = read(cfd, buf, sizeof(buf));
		if(n<=0)
		{
			printf("read error or client close, n==[%d]\n", n);
			break;
		}
		printf("n==[%d], buf==[%s]\n", n, buf);	

		for(i=0; i<n; i++)
		{
			buf[i] = toupper(buf[i]);
		}

		//发送数据
		write(cfd, buf, n);
	}

	//关闭监听文件描述符和通信文件描述符
	close(lfd);
	close(cfd);
	
	return 0;
}
  • Test (using test tool nc) :
nc ip 端口号

Client program example

//客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>

int main()
{
	//创建socket---用于和服务端进行通信
	int cfd = socket(AF_INET, SOCK_STREAM, 0);
	if(cfd<0)
	{
		perror("socket error");
		return -1;
	}

	//连接服务端
	//int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	struct sockaddr_in serv;
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
	printf("[%x]\n", serv.sin_addr.s_addr);
	int ret = connect(cfd, (struct sockaddr *)&serv, sizeof(serv));
	if(ret<0)
	{
		perror("connect error");
		return -1;
	}	

	int n = 0;
	char buf[256];
	while(1)
	{
		//读标准输入数据
		memset(buf, 0x00, sizeof(buf));
		n = read(STDIN_FILENO, buf, sizeof(buf));
		
		//发送数据
		write(cfd, buf, n);

		//读服务端发来的数据
		memset(buf, 0x00, sizeof(buf));
		n = read(cfd, buf, sizeof(buf));
		if(n<=0)
		{
			printf("read error or server closed, n==[%d]\n", n);
			break;
		}
		printf("n==[%d], buf==[%s]\n", n, buf);
	}

	//关闭套接字cfd
	close(cfd);

	return 0;
}
  • Test (using netstat):

  • [Note]: Refer to the dark horse linux C++ tutorial

Guess you like

Origin blog.csdn.net/baidu_41388533/article/details/108943887