一,前言
今天主要记录一下自己看文档解析服务器端代码的过程。Linux里不懂的函数可以直接用
man + 函数
就可以查看相关文档了。接下来先上代码吧。
二,代码
#include <cstdio>
#include <unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
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<sys/types.h>
#include<sys/socket.h>
函数原型:
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<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);
作用:
htonl()将无符号的long从主机字节顺序转换成网络字节顺序
htons()将无符号的short从主机字节顺序转换成网络字节顺序
ntohl()将无符号的long从网络字节顺序转换成主机字节顺序
ntohs()将无符号的short从网络字节顺序转换成主机字节顺序
在接收网络上传来的数据以及给socket创建的变量赋值时使用。
4,bind
头文件:
#include<sys/types.h>
#include<sys/socket.h>
函数原型:
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<sys/types.h>
#include<sys/socket.h>
函数原型:
int listen(int sockfd, int backlog);
作用:
将传入的socket作为一个服务器(被动)的套接字,并使用accpet()用于接收到来的客户端请求。
原型解析:
第一个参数,用socket()函数创建,但是类型必须是SOCK_STREAM or SOCK_SEQPACKET。 第二个参数,用来确定等待队列到底多长。如果许多请求一起到来,而后来的请求超出了listen的消息队列,那么客户端可能会都到一个错误ECONNRE‐FUSED。如果底层支持重传协议,那么这个请求会被忽略,以便客户端尝试重传。
6,accept
头文件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:
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<unistd.h>
函数原型:
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 <unistd.h>
函数原型:
ssize_t write(int fd,const void*buf,size_t count);
参数说明:
fd:是文件描述符(write所对应的是写,即就是1)
buf:通常是一个字符串,需要写入的字符串
count:是每次写入的字节数
返回值:
成功:返回写入的字节数 失败:返回-1并设置errno ps: 写常规文件时,write的返回值通常等于请求写的字节 数count, 而向终端设备或者网络写时则不一定
9,perror
头文件:
#include<stdio.h>
#include<errno.h>
函数原型:
void perror(const char* s)
s字符串用来打印前置信息,如:“create socket failed:”
作用:
将错误信息打印到标准错误输出