Socket API

socket学习

socket(…)

1
2
3
4
#include <sys/socket.h>
int socket (int family, int type, int protocol);
- success: return >= 0
- fail: return -1

family : 协议族,包括IPv4, IPv6…

family 说明
AF_INET IPv4协议
AF_INET6 IPv6协议
AF_LOCAL Unix域协议
AF_ROUTE 路由套接字
AF_KEY 密钥套接字

type : 套接字类型,包括SOCK_STREAM(字节流), SOCK_DGRAM(数据报)….

type 说明
SOCK_STREAM 字节流套接字
SOCK_DGRAM 数据报套接字
SOCK_SEQPACKET 有序分组套接字
SOCK_RAW 原始套接字

protocol : 某个协议类型常值,设置为0以选择给定family和type组合的系统默认值,包括TCP,UPD,SCTP

protocol 说明
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_STCP STCP传输协议

connect(…)

1
2
3
4
#include <sys/socket.h>
int connect (int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
- success: 0
- fail: -1
  • sockfd:socket返回的套接字描述符

  • 2,3是指向一个套接字结构的指针和结构的大小

值得注意的是,connect函数会导致当前套接字从CLOSED状态转移到SYS_SENT状态

若成功,则转移到established(已建立)状态

若失败,则该套接字不可用,必须关闭

我们不能对这样的套接字再次调用connect函数,必须close然后重新调用socket

bind(…)

1
2
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

bind(…)的用处在于把一个协议地址赋予一个套接字

在服务器上,bind要绑定一个众所周知的端口

而在客户端上,这个可以使用内核分配的临时端口

listen()

1
2
#include <sys/socket.h>
int listen(int sockfd, int backlog);

当socket函数创建一个套接字的时候,它就被设置为一个将调用connect的客户套接字

而listen会将其转换成一个被动套接字,从而接受指向该套接字的连接请求

调用listen后,套接字会从close转换到listen状态

backlog:内核应该为相应套接字排队的最大连接数

我们必须认识到,内核为每一个给定的监听套接字维护两个队列

  • 未完成连接队列 : 每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手

  • 已完成连接队列 : 每个已完成三路握手的客户端对应其中一项

当客户的SYN到达时,TCP就会在未完成连接队列中创建一个新项,然后相应三路握手的第二个分节

然后该项就会在队列中一直保留,直到三路握手的第三个分节到达或者超时为止,

此时服务器进入SYN_RECV状态,当服务器未收到客户端的ACK时,重发请求包,一直到超时,才将此条目从未连接队列删除

SYN攻击就是利用这个特性,通过发送大量的半连接请求,耗费CPU和内存资源

这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,目标系统运行缓慢,严重者引起网络堵塞甚至系统瘫痪。

accept()

1
2
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

accept由TCP服务器调用,如果已完成连接队列为空,那么进程将被投入睡眠

如果accept成功,返回值则是一个全新的描述符,代表与客户的TCP连接

EINTR(系统中断)

在处理僵尸进程的时候,我们曾经为处理SIGCHLD信号调用了wait/waitpid函数

这时候,系统为了处理这个信号产生了一次中断,这会使得accept产生一个EINTR错误

有些内核可以自动重启某些被中断的系统调用,但是为了保证移植性(因为某些内核不支持重启),我们必须对返回的ENITR有所处理

1
2
3
4
5
6
7
8
while (1) {
if ((connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {
if (errno == EINTR)
continue;
else
err_sys("accept error");
}
}

close()

1
2
#include <unistd.h>
int close(int sockfd);

将套接字标记成已关闭,然后立即返回到调用进程

fork(…) & exec(…)

1
2
3
4
5
6
#include <unistd.h>
pid_t fork(void);

int execl(...);
int execv(...);
....具体查阅exec函数族

fork()调用一次,返回两次

  • 第一次在父进程返回,返回的是新生进程的ID

  • 第二次在新生进程返回0

两次返回的意义非常明显,为了区分当前进程是父进程还是子进程,以此来执行不同的操作

fork的两种常见用法

  • 创建一个自身副本

  • 执行其他程序

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
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include <string.h>
#include <unistd.h> //fork
#include <stdlib.h>
#include <sys/wait.h> //waitpid

int run(char *command,char *args) {
pid_t pid;
int pip[2];
char string[100];
char *argv[5];
//获取程序名字
argv[0] = (char *) malloc (100 * sizeof(char));
strcpy (argv[0], command+2);
//获取参数
int i = 1;
argv[i] = (char *) malloc (10 * sizeof(char));
bzero(argv[i], sizeof(argv[i]));
char *p = argv[i];
for (i = 1; *args != '\0' && *args != '\n' && i < 5; args++) {
if (*args == ' ') {
i++;
memset(argv[i], '\0', sizeof(argv[i]));
p = argv[i];
} else {
*p = *args;
p++;
}
}
argv[++i] = NULL;
//创建子进程
pipe(pip);
pid = fork();
if (pid == 0) {
close(pip[0]);
if (pip[1] != STDOUT_FILENO) {
if (dup2(pip[1], STDOUT_FILENO) != STDOUT_FILENO)
return -1;
close(pip[1]);
}
execv(command,argv);
} else if (pip > 0) {
waitpid(pid, NULL, 0);
bzero(string, sizeof(string));
read(pip[0], string, sizeof(string));
close(pip[1]);
close(pip[0]);
printf("Program OUT >>\n%s", string);
setbuf(stdin, NULL);
} else {
printf("i am dead!\n");
}
return 0;
}

sockaddr_in

1
2
3
4
5
6
struct   sockaddr_in   {
short int sin_family; //2
unsigned short int sin_port; //2
struct in_addr sin_addr;//4
unsigned char sin_zero[8]; //8
};

test code

Server.cpp

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
#include <cstring> //use bzero

#include <memory.h> //use memset
#include <unistd.h> //use fork, exec, read, write, close
#include <arpa/inet.h> //use the struct, such as sockaddr
#include <sys/socket.h> //use socket, bind, connect, listen, accept..

class Server {
private:
int serv_sock;
char receive[100];
sockaddr_in serv_addr;
sockaddr_in clnt_addr;
socklen_t clnt_addr_size;
pid_t childpid;
public:
void init();
void wait();
};

void Server::init()
{
serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//memset(&serv_addr, 0, sizeof(serv_addr)); use bzero
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; //use IPv4 address
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //server IP address
serv_addr.sin_port = htons(1234); //port
}

void Server::wait()
{
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
listen(serv_sock, 20);

while (1) {
clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if ((childpid = fork()) == 0) {
read(clnt_sock, receive, 100);
std::cout << receive << std::endl;
write(clnt_sock, receive, sizeof(receive));
memset(receive, 0, sizeof(receive));
exit(0);
}
close(clnt_sock);
}
}

int main() {
Server myserver;
myserver.init();
myserver.wait();
return 0;
}

Client

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
#include <cstdlib>
#include <memory.h> //使用memset()
#include <unistd.h> //use fork,exec, read, write, close
#include <arpa/inet.h> //use the some struct
#include <sys/socket.h> //use socket, bind, connect
//#include <pthread.h>

class Clinet {
private:
char receive[100];
char sent[100];
int serv_sock;
sockaddr_in serv_addr;
public:
Clinet(std::string server_ip = "127.0.0.1");
void connecting();
};

Clinet::Clinet(std::string server_ip) {
const char *ip = server_ip.c_str();
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr(ip); //具体的IP地址,服务器的ip地址
serv_addr.sin_port = htons(1234); //端口,服务器开放的端口
}
void Clinet::connecting() {
memset(&sent, 0, sizeof(sent));
memset(&receive, 0, sizeof(receive));
while (1) {
int sock;
//创建套接字
if( (sock = socket(AF_INET, SOCK_STREAM, 0)) < 0){
std::cout << "creat socket fail";
exit(0);
}
//读取服务器传回的数据
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0){
std::cout << "can not connect" << std::endl;
exit(0);
}
std::cin >> sent; //输入要发送的数据
write(sock, sent, sizeof(sent)); //发送数据
read(sock, receive, sizeof(receive)); //接收数据
std::cout << "message :" << receive << std::endl;
memset(&sent, 0, sizeof(sent));
memset(&receive, 0, sizeof(receive));
//关闭套接字
close(sock);
}
}
using namespace std;
int main(int argc, char const *argv[]) {
Clinet myclinet;
std::string MyIp;
std::cout << "Enter the ip to connect(1:use the default ip):";
std::cin >> MyIp;
if (MyIp == "1") {
myclinet = Clinet();
} else {
myclinet = Clinet(MyIp);
}
myclinet.connecting();
return 0;
}