【Linux网络编程】剖析服务器端代码

【Linux网络编程】剖析服务器端代码_第1张图片

   一,前言   

        今天主要记录一下自己看文档解析服务器端代码的过程。Linux里不懂的函数可以直接用

man + 函数

 就可以查看相关文档了。接下来先上代码吧。

二,代码

#include 
#include 
#include
#include
#include
#include

int main()
{
	char buffer[50] = { 0 };
	int res = 0;
	int server_socket;//socket网络描述符,也叫套接字描述符
	int accept_socket;
	//第一步创建套接字描述符
	printf("开始创建tcp服务器!\n");
	server_socket = socket(AF_INET, SOCK_STREAM, 0);//要想向网络发送数据都是用server_socket

	if (server_socket < 0) {
		perror("socket create failed:");
		return 0;
	}

	//第二步:要告诉服务器,我的ip地址和端口号。用一个变量来保存
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = INADDR_ANY; //INADDR_ANY告诉系统自动绑定网卡的IP地址
	server_addr.sin_port = htons(66666);//网络地址转换,把主机字节顺序转换成网络字节顺序
	
	//第三步:把设定好的ip地址和端口号绑定到我们的server_socket描述符上
	if (bind(server_socket,(struct sockaddr*) & server_addr, sizeof(server_addr)) < 0) {
		perror("server bind error:");
		return 0;
	}

	//第四步:调用listen开始监听程序
	if (listen(server_socket, 10) < 0) {
		perror("server listen error:");
		return 0;
	}

	//第五步:等待客户端连接
	//accpet函数在未接收客户端连接时为阻塞状态,接收到连接时解阻塞并返回一个新的套接字描述符
	printf("Tcp服务器准备完成,等待客户端连接!\n");
	accept_socket = accept(server_socket, NULL, NULL);
	printf("有客户端连接到服务器!\n");
	while (1) {
		//read函数接收客户端发送的数据,返回值表示实际从客户端收到的字节数
		//buffer:就是收到客户端数据后把数据存放的地址,sizeof(buffer)是希望读取的字节数
		res = read(accept_socket, buffer,sizeof(buffer));
		printf("client read %s\n",buffer);
		write(accept_socket, buffer, res);
		memset(buffer, 0, sizeof(buffer));
	}
    printf("%s 向你问好!\n", "Linux");
    return 0;
}

三,函数解析

下面是代码中一些相关函数的解析,建议是自己去看看文档,下面是是英文文档自己翻译出来的,如果有错误和翻译得不对的地方,欢迎大家在评论区指出。

1,Socket

头文件:

#include
#include
函数原型:

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

作用:

创建一个用于通信的套接字,并返回一个文件(套接字)描述符[int类型]。

原型解析:

domain(域):可选如下

Name Purpose
AF_UNIX,AF_LOCAL Local commucation
AF_INET IPv4 Internet protocols
AF_INET6 IPv6 Internet protocols
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device
AF_X25 ITU-T X.25 / ISO-8208 protocol
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK AppleTalk
AF_PACKET Low level packet interface
AF_ALG Interface to kernel crypto API

type(类型):可选如下

Name
SOCK_STREAM Provides sequenced,reliable,two-way, connection-based byte streams。(实际上就是提供TCP流服务)
SOCK_DGRAM Supports datagrams(connectionless,unreliable messages of a fixed maximum length).(提供数据报服务)
SOCK_SEQPACKET Provides a sequenced, reliable ,two-way connection-based data transmission path for datagrams of fixed maximum length;
SOCK_RAW Provides raw network protocol access
SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering
SOCK_PACKET Obsolete and should not be used in new programs;(已淘汰,不使用)
SOCK_NONBLOCK Set the O_NONBLOCK file status flag on the new open file description.暂时用不到
SOCK_CLOEXEC Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor.暂时用不到

2,socketaddr_in

struct sockaddr {
    sa_falmily_t    sa_family;//协议簇
    char            sa_data[14];//协议地址和端口号
}
​
//端口号和ip地址混合在一起

内部定义结构体

struct sockaddr_in {
    sa_family_t sin_family;//协议簇
    uint16_t    sin_port;//端口
    struct      in_addr sin_addr;//地址
    char        sin_zero[8]; //不使用
}
​
struct in_addr
{
    In_addr_t   s_addr;//32位IPv4地址
};

这个变量用来存储ip地址和端口号,以及告诉系统使用什么协议进行通信。

注意,端口号赋值的时候需要转换。因为计算机有可能用不同的字节顺序存储,如果在网络中传递字节,就必须用网络里的字节传播。否则有可能乱码。使用htons()函数和ntohs()函数。就是network to host short,或者host to network short。

使用的时候,sin_addr.s_addr=INADDR_ANY,这个数值告诉系统,自动取绑定网卡。

3,htons,ntohs,htonl,ntohl

头文件:

#include

函数原型:

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

作用:

htonl()将无符号的long从主机字节顺序转换成网络字节顺序

htons()将无符号的short从主机字节顺序转换成网络字节顺序

ntohl()将无符号的long从网络字节顺序转换成主机字节顺序

ntohs()将无符号的short从网络字节顺序转换成主机字节顺序

在接收网络上传来的数据以及给socket创建的变量赋值时使用。

4,bind

头文件:

#include
#include

函数原型:

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

作用:

使用socket创建一个套接字之后,它是没有ip地址和端口号需要使用bind函数将端口号和ip地址绑定到创建的socket上。

原型解析:

第一个参数是int型,实际上就是之前用socket()函数创建出来的文件描述符,返回值就是int。第二个参数需要传入一个socketaddr结构体指针,就是之前创建的socket_addr_in结构体,在其中已经将协议,端口号,ip地址都写完了。第三个参数是第二个参数的大小,使用sizeof()就可以了。

5,listen

头文件:

#include
#include

函数原型:

int listen(int sockfd, int backlog);

作用:

将传入的socket作为一个服务器(被动)的套接字,并使用accpet()用于接收到来的客户端请求。

原型解析:

    第一个参数,用socket()函数创建,但是类型必须是SOCK_STREAM or SOCK_SEQPACKET。
    第二个参数,用来确定等待队列到底多长。如果许多请求一起到来,而后来的请求超出了listen的消息队列,那么客户端可能会都到一个错误ECONNRE‐FUSED。如果底层支持重传协议,那么这个请求会被忽略,以便客户端尝试重传。

6,accept

头文件:

#include 
#include 

函数原型:

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

作用:

当socket模式设置为阻塞,accept函数的功能是阻塞等待client发起三次握手,当3次握手完成的时候,accept解除阻塞,并从全连接队列中取出一个socket,就可以对这个socket连接进行读写操作

解析:

sockfd:fd
​
addr: 用来接收对端的连接地址信息
​
addrlen:addr的长度
​
返回值:成功则返回一个新的fd,这个fd用来和对端进行通信;失败则返回-1,并且设置errno;

7,read

头文件:

#include

函数原型:

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

作用:

尝试去fd(一般是socket()套接字)读取count字节数据到缓冲区buf。

在支持查找的文件中,读取操作从文件指针+偏移量的位置开始,按所需字节读取。若位置超过或者等于文件末尾,则不读取并返回0。

如果count = 0,函数可能报错。

若读取成功,函数会返回实际读取的字节数。若失败,则会返回值-1.

解析:

fd : 套接字描述符;
buf: 创建的缓冲区的指针。
count:希望读入的字节数。

错误类型

Name Description
EAGAIN 文件描述符指向一个文件并且这个文件被设置为非阻塞,但read是阻塞式读
EAGAIN 文件描述符指向一个套接字并且这个文件被设置为非阻塞,但read是阻塞式读
EBADF fd不是一个可用的文件描述符,或者不能用于读
EFAULT 缓冲区超过了可用的地址空间
EINTR 在数据被读取之前,该函数被中断了。
EINVAL 文件描述符指向了一个不能读的地址。或者指向的地方不适合对齐读写
EINVAL fd由timerfd_create()创建并且错误的缓冲区大小被用于read函数
EIO I/Oerror
EISDIR fd指向文件夹

8,write

头文件:

#include 

函数原型:

ssize_t write(int fd,const void*buf,size_t count);
参数说明:
  fd:是文件描述符(write所对应的是写,即就是1)
  buf:通常是一个字符串,需要写入的字符串
  count:是每次写入的字节数

返回值:

 成功:返回写入的字节数
 失败:返回-1并设置errno
  ps: 写常规文件时,write的返回值通常等于请求写的字节
       数count, 而向终端设备或者网络写时则不一定

9,perror

头文件:

#include
#include

函数原型:

void perror(const char* s)
 s字符串用来打印前置信息,如:“create socket failed:”

作用:

将错误信息打印到标准错误输出

四,结果

【Linux网络编程】剖析服务器端代码_第2张图片

你可能感兴趣的:(Linux网络编程,linux,服务器,c++)