C++ 网络编程

1. socket概念

socket是一种进程通信机制,被称为是套接字,引入一个标准名字空间进行进程之间的通信。

socket相当于是在传输层协议TCP/UDP之上的一层抽象层级更高的协议,方便应用程序进行使用。

socket主要使用在客户/服务器模型而设计的,针对客户和服务器程序需要提供不同的socket系统调用,运行在
客户端的被称为是clientSocket,运行在服务器端的是serverSocket

套接字之间的连接主要分为三个步骤

  1. 服务器监听:服务器端套接字不定位具体的客户端套接字,而是处于等待的状态,实时监控网络状态
  2. 客户端请求:是指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。
    为此客户端的套接字必须描述其要连接的服务器的套接字,指出服务器套接字的地址和端口号,然后
    提出连接请求
  3. 连接确认:是指当服务器套接字监听到或者说接收到客户端套接字的连接请求,就响应客户端
    套接字的请求,建立一个新的进程,把服务器套接字的描述发送给客户端,一旦客户端确认了次描述
    连接就建立完成。

img

2. socket 函数

函数原型

1
2
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

domain

type

return

返回的是套接字的文件描述符,当无法创建的时候,返回-1

protocol 一般情况下都只能设置为0

对于socket地址结构体为sockaddr_in
sockaddr_in的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */

/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr)
- __SOCKADDR_COMMON_SIZE
- sizeof (in_port_t)
- sizeof (struct in_addr)];
};

__SOCKADDR_COMMON其实就是sin_family表示其是地址族。

in_port_t 表示是端口号

in_addr 中是ip地址

1
2
3
4
5
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};

对于in_addr的取值一般使用INADDR_ANY表示对所有地址都开放。

3. 网络序列和主机序列的转换

htonl 将无符号的主机长整数从主机字节序转换为网络字节序host to net long

htons 将无符号的主机短整型从主机字节序转换为网络字节序 host to net short

ntohl 从网络长到主机长

ntohs 从网络短到主机短

2. echo 服务器实现

2.1 server

对于server主要分为个部分

  • 创建套接字
1
2
3
4
5
6
7
8
sockaddr_in server_addr;
char buf[BUFSIZE];
serverfd = socket(AF_INET, SOCK_STREAM, 0); // 返回描述符
if (serverfd == -1) {
printf("Create socket error(%d):%s\n",errno, strerror(errno));
return -1;
}
bzero(&server_addr, sizeof(server_addr));
  • 配置套接字
1
2
3
server_addr.sin_family = AF_INET; // 使用ipv4协议
server_addr.sin_port = htons(DEFAULT_PORT); // 使用默认的端口
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 接收任何连接
  • 绑定,将本地地址给套接字的文件描述符
1
int bind(int socket, const struct sockaddr *address,socklen_t address_len);

socket: socket的文件描述符
addresssockaddr类型的地址
address_len:sockaddr的长度

1
2
3
4
if (bind(serverfd, (sockaddr*)&server_addr, sizeof(server_addr))== -1) {
printf("Bind error(%d): %s\n",errno, strerror(errno));
return -1;
}
  • 监听端口
    监听某个套接字同时限制连接的数目。
1
2
3
4
if (listen(serverfd, MAXLINK) == -1) {
printf("Listen error(%d): %s\n", errno, strerror(errno));
return -1;
}
  • 循环处理,进行处理
1
int accept(int socket, struct sockaddr *restrict address,socklen_t *restrict address_len);

接收来自套接字的一个新的连接,address中存储的是连接的sockaddr返回地址,address_len
输入提供的返回地址的长度。

1
ssize_t recv(int socket, void *buffer, size_t length, int flags);

从套接字中获取数据,flag设置为0即可

1
ssize_t send(int socket, const void *buffer, size_t length, int flags);

发送数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
while (1)
{
/* code */
signal(SIGINT, stopServer);
clientfd = accept(serverfd, NULL, NULL);
if (clientfd == -1) {
printf("Accept error(%d):%s\n",errno, strerror(errno));
return -1;
}
bzero(buf, BUFSIZE);
recv(clientfd, buf, BUFSIZE-1, 0);

printf("Recv: %s\n",buf);
send(clientfd, buf, strlen(buf), 0);
close(clientfd);
}

2.2 clinet

client的逻辑

  • 创建套接字
1
2
3
4
5
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
if (clientfd == -1) {
printf("Create socket error(%d):%s\n", errno, strerror(errno));
return -1;
}
  • 连接套接字

    将客户端的套接字和服务器端的套接字进行相连

    对于客户端连接的时候需要将字符串形式的ip地址转换为网络地址类,函数原型如下

    1
    int inet_pton(int af, const char *restrict src, void *restrict dst);

    af: 只能为ipv4或者ipv6,即AF_INET / AF_INET6

    src: 服务器的ip地址

    dst: 套接字的网络地址

    1
    int connect(int socket, const struct sockaddr *address, socklen_t address_len)

socket:套接字的文件描述符

address:分支的地址

address_len: 地址的大小

1
2
3
4
if (connect(clientfd, (sockaddr*)&server_addr,sizeof(server_addr))==-1) {
printf("Connect error (%d):%s\n",errno, strerror(errno));
return -1;
}
  • 发送数据

    使用send函数

  • 接收数据

    使用recv函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <cstdio>
#include <error.h>
#include <cstring>
#include <cstdlib>
#include <sys/socket.h>
#include <sys/unistd.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <netinet/in.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUFSIZE 2048
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
int main() {
sockaddr_in server_addr;
char buf[BUFSIZE];
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
if (clientfd == -1) {
printf("Create socket error(%d):%s\n", errno, strerror(errno));
return -1;
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);
server_addr.sin_port = htons(SERVER_PORT);
if (connect(clientfd, (sockaddr*)&server_addr,sizeof(server_addr))==-1) {
printf("Connect error (%d):%s\n",errno, strerror(errno));
return -1;
}
printf("Please input:");
scanf("%s",&buf);
send(clientfd, buf, strlen(buf),0);
bzero(buf, sizeof(buf));
recv(clientfd, buf, BUFSIZE - 1, 0);
printf("Recv: %s\n", buf);
close(clientfd);
return 0;

}

2.3 test

img

3. 添加路由请求

1
2
3
4
5
6
7
8
void setResponse(char *buff) 
{
bzero(buff, sizeof(buff));
strcat(buff, "HTTP/1.1 200 OK\r\n");
strcat(buff, "Connection: close\r\n");
strcat(buff, "\r\n");
strcat(buff, "Hello\n");
}

在我们使用send函数之前使用这个函数进行处理,在浏览器中进行访问得到hello,后面我会继续完善