Linux操作系统网路编程基础——TCP微服务器实现
前言
这算是我自己的一个Linux网络编程学习路上的一个学习笔记,学习的过程中看过一些视频+博客,所以在学习过后根据记录的笔记来完成代码实现的过程中,可能会出现一大段文章内容和别人写的一样或者某些思想也会相同,如有侵权,请联系删除或者添加引用。(本文章不会作为商业用途)
一、TCP协议
(1)三次握手——建立连接
从网络编程程序员的角度来看,我们自己一般不会实现三次握手建立连接的这个步骤,因为对于服务器来说,这个过程封装到了accept()函数当中,对于客户端而言,这个过程封装到了conne()函数中。我们调用这两个函数过后,系统会自动使用三次握手连接对端。
(2)四次挥手——断开连接
二、重要函数介绍
1.网络字节序和本地字节序相互转换
在网络通信中,数据的存储方式为大端法:高位存低地址,低位存高地址。
在计算机上,数据的存储方式为小端法:高位存高地址,低位存低地址。
所以在通信过程中,我们需要进行网络字节序和本地字节序进行相互转换。用到的函数如下:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
//32位的本地字节序转网络字节序,通常用于对IP地址进行转换。
uint16_t htons(uint16_t hostshort);
//32位的本地字节序转网络字节序,通常用于对端口号进行转换。
uint32_t ntohl(uint32_t netlong);
//网络字节序转本地字节序
uint16_t ntohs(uint16_t netshort);
//网络字节序转本地字节序
int inet_pton(int af, const char *src, void *dst);
//将点分十进制的IP地址转换为网络字节序
//参1:af:ip协议版本,v4或者;参2:src:字符串首地址,或者点分十进制的IP地址字符串;参3:dst:转换后的结果。
//返回值:成功(1);异常(0);失败(-1)。
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
//将网络字节序转换为点分十进制的IP地址
//参1:IP版本。参2:要转换的网络字节序。参3:转换后的结果。参4:src的字节长度
//返回值:成功(dst)。失败(NULL)。
2.网络编程用到的函数
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//创建套接字
//参1:协议簇,可以选择AF_INET、AF_INET6
//参2:指定socket通信时的类型SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等,一般选用流式SOCK_STREAM
//参3:指定网络通信使用的协议,一般传0。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//绑定服务器的IP地址和端口号(IP+端口号称为地址结构)
//参1:socket()函数创建的套接字(返回值)
//参2:服务器的地址结构
//参3:地址结构的长度
//IP地址可以确定唯一的一台主机,端口号可以确定一台主机上唯一的一个应用程序。两者结合可以确定网络中唯一一台主机上的唯一一个程序。
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
//参2:设置sockfd的监听上限
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//等待客户端申请连接的请求,如果有请求则进行三次握手操作来连接
//参1:socket()函数创建的套接字(返回值)
//参2:客户端的地址结构
//参3:地址结构的长度
3.读写函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//从sockfd的读缓冲区向内存中读取数据
//参1:socket()函数创建的套接字(返回值)
//参2:buf用来保存读取到的数据,为传出参数
//参3:buf的长度
//参4:一般设置为0,默认读取方式,等同于read()函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//向sockfd的写缓冲区中写入数据
//参1:socket()函数创建的套接字(返回值)
//参2:buf表示需要向写缓冲区写入的内容
//参3:需要写入的字节长度
//参4:一般设置为0,默认读取方式,等同于write()函数
三、TCP微型服务器实现
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <ctype.h>
#define SERVER_PORT 9999
using namespace std;
int main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("socket error");
exit(1);
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(sockfd, (sockaddr*)&server_addr, sizeof(server_addr));
if (ret == -1)
{
perror("bind error");
exit(1);
}
ret = listen(sockfd, 64);
if (ret == -1)
{
perror("listen error");
exit(1);
}
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int cfd = accept(sockfd, (sockaddr*)&client_addr, &client_addr_len);
if(cfd == -1)
{
perror("accept error");
exit(1);
}
string client_ip;
const char *p = inet_ntop(AF_INET, &client_addr, &client_ip[0], client_addr_len);
cout << "p=" << p<<endl;
cout<<"client_ip="<<client_ip.c_str()<<endl;
char buff[1024];
while(1)
{
ssize_t ret1 = recv(cfd, buff, sizeof(buff),0);
cout<<buff<<endl;
if(ret1 == -1)
{
perror("recv error");
exit(1);
}
for(int i = 0; i < ret1; i++)
{
buff[i] = toupper(buff[i]);
}
write(STDOUT_FILENO, buff, ret1);
send(cfd, buff, ret1, 0);
}
close(sockfd);
close(cfd);
return 0;
}
四、实验时遇到的问题
为了更像是个C++程序员而不是C程序员,我本来是想做以下替换:
第一步,把while循环上面的一句 char buff[1024] 改为 string buff。
第二步,while循环中,改为 ssize_t ret1 = recv(cfd, &buff[0], buff.size(),0),并且输出一下,cout<< buff.c_str() <<endl。但是在这一步,就开始无法输出 buff 字符串了。
第三步,while 循环中的 for 循环这样改
for(string::iteartor it = buff.begin(); it != buff.end(); it++)
{
*it = toupper(*it);
}
write(STDOUT_FILENO, &buff[0], ret1);
send(cfd, &buff[0], ret1, 0);
经过第三步这样改了过后,客户端的一方也收不到我发出去的数据。
要是有大佬看到我这篇文章,希望您能指教一下,谢谢!