0%

C++简单socket编程

简单的socket编程

复习下C++的socket编程

step 1:调用socket()函数创建套接字

Linux系统下的socket API。

1
2
3
#include <sys/socket.h>
// 成功返回文件描述符
int socket(int domain,int type,int protocol)

int domin:即协议域,又称为协议族。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。通常情况下我们使用AF_INET。

int type:是套接口类型,主要SOCK_STREAM(建立TCP连接)、SOCK_DGRAM(建立UDP)、SOCK_RAW;SOCK_STREAM(建立TCP连接):提供面向连接的可靠的数据传输服务。数据被看作是字节流,无长度限制。例如FTP协议就采用这这种。SOCK_DGRAM(建立UDP):提供无连接的数据传输服务,不保证可靠性。SOCK_RAW:该接口允许对较低层次协议,如IP,ICMP直接访问。

int protocol:指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。通常情况系设为0。

注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

step 2:调用bind()函数初始化套接字的IP地址和端口号

1
2
3
#include <sys/socket.h>
// 成功返回0,失败返回-1
int bind(int sockfd,struct sockaddr *myaddr,socklen_t addrlen)

调用bind函数为创建好的socket套接字分配网络IP地址和端口,第一个参数sockfd就是socket函数返回的文件描述符(用于找到是哪个socket)。

第二个参数就相当重要了,首先需要借助\中的sockaddr_in来填写这个sockaddr

1
2
3
4
5
6
7
8
9
10
11
struct sockaddr_in serv_addr;
memset(&serv_addr,0,sizeof serv_addr);
serv_addr.sin_family=AF_INET;
// 选择IP
serv_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
// 设置接收消息的端口号
serv_addr.sin_port=htons(atoi(argv[1]));
// 绑定端口
if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof serv_addr)<0) {
error_handle("bind","bind() error.");
}

为什么serv_addr可以直接强制转换成sockaddr呢?先看下源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* /usr/include/bits/socket.h */
/* Structure describing a generic socket address. */
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};

/* /usr/include/netinet/in.h */
/* Structure describing an Internet socket address. */
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无非就是将sockaddr_in这个结构体变成了char数组(方便传输吧),两者的大小都是一样的,所以可以直接转换。

htons函数的拓展(摘录):

不同的计算机里对它们的多字节整数(例如:一个大于char的整数)使用不同的字节顺序。这就意 味着,如果你从intel的盒子中send()一个两个字节的短整数到一个Mac中(在它们成为Intel之前),一个计算机认为是数字1,而另外一个计 算机会认为它是数字256,反之亦然。

解决这个问题的办法是:所有的人撇开它们的不同之处,同意摩托罗拉和IBM的顺序是正确的,而Intel使用的是怪异的方式,所以我们在将他们发送 出去之前把所有的字节以“正序”方式排列。既然Intel是一个“反序”的机器,所以调用我们需要的,以“网络字节顺序”排列的字节是很正确的。所以这些 函数把你的本机字节顺序转化为网络字节顺序然后再转化回来。

(这就意味着,在Intel上面,这些函数把所有的字节调换过来,而在PowerPC上面,它们什么都没有做,因为在那上面字节本身是以网络字节顺序排列的。但是你依然要在你的程序当中使用它们,因为别的人有可能将它们应用到Intel机器当中,使之依然正常的运行。)

注意到这些类型包含32-位(4个字节,可能是整数)和16-位(两个字节和short很像)数据。64-位的机器可以使用htonll()作为64-位整数,但是我们没有见到它。你必须自己写。

不管怎样,使用这些函数决定于你要从主机字节顺序(你的电脑上的)还是网络字节顺序转化。如果是”host”,函数的第一个字母为”h”,否 则”network”就为”n”。函数的中间字母总是”to”,因为你要从一个转化到另一个,倒数第二个字母说明你要转化成什么。最后一个字母是数据的大 小,”s”表示short,”l”表示long。于是:

1
2
3
4
htons()    // host to network short
htonl() // host to network long
ntohs() // network to host short
ntohl() // network to host long

返回值:

每个函数返回转化后的值

step 3:调用listen()函数将socket套接字转为可接收请求状态

1
2
3
#include <sys/socket.h>
// 成功返回0,失败返回-1
int listen(int sockfd,int backlog);

前面完成对socket套接字的网络IP地址和端口绑定后,通过调用listen函数使socket套接字进入等待接听的状态,第二个参数设置同一时间运行等待接听的对方机器数量(对方建立连接前会处于等待队列中)

step 4:调用accept()函数接收连接请求,并返回能够正常进行数据通信的文件描述符

1
2
3
#include <sys/socket.h>
// 成功返回文件描述符,失败返回-1
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);

进入等待接听后,调用accept()函数,正常情况下回进入阻塞状态,等待连接到达这个socket绑定的IP端口上,一旦接收到对方请求就会返回一个建立正常数据通信的文件描述符供双方使用,并且会将对方的IP地址及端口信息使用指针传参的方式传递回来。

Example

服务端

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
#include <iostream>
#include <string>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void error_handle(std::string opt,std::string message) {
// 打印到终端
perror(opt.c_str());
std::cout << message << std::endl;
exit(1);
}

int main(int argc,char* argv[]){
int serv_sock;
int client_sock;

struct sockaddr_in serv_addr;
struct sockaddr_in client_addr;

socklen_t client_addr_size;
char message[]="hello world";

// 需要在命令行输入端口号,不然就报错退出
if(argc<2) {
std::cout << "Usage : " << argv[0] << " <port>" << std::endl;
exit(1);
}

serv_sock=socket(PF_INET,SOCK_STREAM,0);
if(serv_sock<0) {
error_handle("socket","socket() error.");
}
memset(&serv_addr,0,sizeof serv_addr);
serv_addr.sin_family=AF_INET;
// 选择IP
serv_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
// 设置接收消息的端口号
serv_addr.sin_port=htons(atoi(argv[1]));
// 绑定端口
if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof serv_addr)<0) {
error_handle("bind","bind() error.");
}
if(listen(serv_sock,5)<0) {
error_handle("listen","listen() error.");
}
std::cout << "Waiting Client......" << std::endl;
// accept()建立成功后,服务器可以获取客户端的IP和端口
client_sock=accept(serv_sock,(struct sockaddr*)&client_addr,&client_addr_size);
if(client_sock<0){
error_handle("accept","accept() error");
}
// 打印客户端的IP和端口,ntoa:标准net格式转为字符串,ntohs:标准net转为host short类型
std::cout << "Client IP : " << inet_ntoa(client_addr.sin_addr) << ", port : " << ntohs(client_addr.sin_port) << std::endl;
// 向客户端发送消息
write(client_sock,message,sizeof message);
// 关闭TCP连接
close(client_sock);
// 关闭socket套接字
close(serv_sock);
return 0;
}

客户端

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
#include <iostream>
#include <string>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void error_handle(std::string opt,std::string message){
perror(opt.c_str());
std::cout << message << std::endl;
exit(1);
}

int main(int argc,char* argv[]){
int sock;
struct sockaddr_in serv_addr;
char message[64];
int str_len;
// 判断命令行参数
if(argc<3){
std::cout << "Usage : " << argv[0] << " <IP> <port>" << std::endl;
exit(1);
}
// 创建socket套接字
sock=socket(PF_INET,SOCK_STREAM,0);
if(sock<0){
error_handle("socket","socket() error.");
}
// 初始化服务器套接字结构体参数,设置对方的IP和端口
memset(&serv_addr,0,sizeof serv_addr);
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
serv_addr.sin_port=htons(atoi(argv[2]));
// 与服务器建立连接
if(connect(sock,(struct sockaddr*)&serv_addr,sizeof serv_addr)<0){
error_handle("connect","connect() error.");
}
// 最后一个位置要留给'\0'
str_len=read(sock,message,sizeof(message)-1);
if(str_len<0){
error_handle("read","read() error.");
}
std::cout << "Recv Message : " << message << std::endl;
// 关闭套接字
close(sock);
return 0;
}