Linux网络编程套接字

网络编程套接字

udp传输

客户端

1、创建套接字 socket()

2、为套接字绑定地址 bind()

3、发送数据(如果socket还没有绑定地址,这时候操作系统会选择一个合适的地址端口进行绑定)

4、接收数据

5、关闭套接字

服务端

1、创建套接字,通过创建套接字使进程与网卡建立联系,创建struct socket{…}

2、为套接字绑定地址信息

3、接收数据

4、发送数据

5、关闭套接字

创建套接字

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domin: 地址域
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
type:套接字类型
SOCK_STREAM 流式套接字,默认协议TCP,不支持UDP
SOCK_DGRAM 数据报套接字,默认协议UDP,不支持TCP
protocol:协议类型
0:使用默认套接字协议
6/IPPOTO_TCP TCP协议
17/IPPOTO_UDP UDP协议
返回值:套接字操作句柄-文件描述符 失败返回-1

为套接字绑定地址

1
2
3
4
5
6
7
8
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
bind()函数应将本地套接字地址地址分配给由描述符套接字标识的套接字,该套接字没有分配本地套接字地址.使用socket()函数创建的套接字最初是未命名的;它们仅由其地址族标识。

socket:套接字文件描述符
sockaddr:地址信息
addrlen:地址信息长度

返回值:0 失败返回-1
详细了解地址家族

在bind函数中我们看到了sockaddr这个关键的地址家族。通过man手册只能找到少量的描述。通过查找资料找到了一些,再此做出总结

一般我们使用struct sockaddrstruct sockaddr_in这两个结构体用来处理网络通信的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct sockaddr
{
unsigned short sa_family; //2
char sa_data[14]; //14
};
该结构体的缺陷为sa_data[]把目标地址和端口信息混在一起了
上面是通用的socket地址,具体到Internet socket,用下面的结构,二者可以进行类型转换
struct sockaddr_in
{
sa_family_t sin_family;//地址族
uint16_t sin_port;//16位TCP/UCP端口号
struct in_addr sin_addr;//32位IP地址
char sin_zero[8];//不使用
}
在这个结构体中存在另一个结构体,该结构体一般用来存放32位IP地址
struct in_addr
{
In_addr_t s_addr;//32位IPV4地址
}

sin_port和sin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。

1
2
3
4
5
6
7
8
9
10
一般用法
int sockfd;  
struct sockaddr_in   my_addr;  //赋值时用这个结构
sockfd = socket(AF_INET,   SOCK_STREAM,   0);      
my_addr.sin_family   =   AF_INET;     
my_addr.sin_port   =   htons(MYPORT);     
my_addr.sin_addr.s_addr   =   inet_addr("192.168.0.1");     
bzero(&(my_addr.sin_zero),   8);         
bind(sockfd,   (struct   sockaddr   *)&my_addr,   sizeof(struct   sockaddr));
//用(struct   sockaddr   *)转换即满足要求

还有网络上另一个其他解释

1
struct sockaddr 是一个通用地址结构,这是为了统一地址结构的表示方法,统一接口函数,使不同的地址结构可以被bind() , connect() 等函数调用;struct sockaddr_in中的in 表示internet,就是网络地址,这只是我们比较常用的地址结构,属于AF_INET地址族,他非常的常用,以至于我们都开始讨论它与 struct sockaddr通用地址结构的区别。

接收数据

1
2
3
4
5
6
7
8
9
10
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);

sockfd:套接字文件描述符
buf:用buf存储接收的数据
len:想要接收的数据长度
flags:0---默认阻塞接收
src_addr:发送端地址信息
addrlen:地址信息长度,不但要指定想接收多长,还要保存实际接受了多长
返回值:实际接收的长度 失败返回-1

发送数据

1
2
3
4
5
6
7
8
9
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd:套接字文件描述符
buf:要发送的数据
len:要发送的数据长度
flags:0---默认阻塞发送
dest_addr:目的段地址信息--标识数据要发送到哪里去
addrlen:地址信息长度
返回值:实际发送的长度 失败返回-1

关闭套接字

1
2
int close(int fd);
这个在Linux文件操作符中已经了解到了,文件描述符用完之后需要被关闭

UDP传输Demo

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//UdpSocket.hpp
//实现以UdpSocket类封装udp常用操作
#include <iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
class UdpSocket{
public:
UdpSocket():_sock(-1){}
~UdpSocket(){}
bool Socket() {
_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (_sock < 0) {
perror("socket error");
return false;
}
return true;
}
bool Bind(string &ip, uint16_t port) {
struct sockaddr_in addr;//定义一个结构体名称为addr,是socket地址
//in 表示internet,就是网络地址
//sin_family指代协议族,在socket编程中只能是AF_INET
//sin_port存储端口号(使用网络字节顺序)
//sin_addr存储IP地址,使用in_addr这个数据结构

addr.sin_family = AF_INET;//ipv4
//uint16_t htons(uint16_t hostshort);

addr.sin_port = htons(port);//htons是将整型变量从主机字节顺序转变成网络字节顺序,
//就是整数在地址空间存储方式变为高位字节存放在内存的低地址处。
//in_addr_t inet_addr(const char *cp);

addr.sin_addr.s_addr = inet_addr(ip.c_str());//inet_addr是一个计算机函数,
//addr.sin_addr.s_addr = INADDR_ANY;
// 功能是将一个点分十进制的IP转换成一个长整数型数(u_long类型)等同于inet_addr()。

socklen_t len = sizeof(struct sockaddr_in);
//int bind(int sockfd, struct sockaddr *addr,
// socklen_t addrlen);

int ret = bind(_sock, (struct sockaddr*)&addr, len);
if (ret < 0) {
perror("bind error");
return false;
}
return true;
}
bool Recv(string &buf, struct sockaddr_in *saddr) {
//ssize_t recvfrom(int sockfd, void *buf, size_t len,
//int flags, struct sockaddr *src_addr, socklen_t *addrlen);
char tmp[1500] = {0};
socklen_t len = sizeof(struct sockaddr_in);
int ret = recvfrom(_sock, tmp, 1500, 0,
(struct sockaddr*)saddr, &len);
if (ret < 0) {
perror("recvfrom error");
return false;
}
buf.assign(tmp, ret);
return true;
}
bool Send(string &buf, struct sockaddr_in *daddr){
socklen_t len = sizeof(struct sockaddr_in);
int ret = sendto(_sock, buf.c_str(), buf.size(), 0,
(struct sockaddr*)daddr, len);
if (ret < 0) {
perror("sendto error");
return false;
}
return true;
}
bool Close() {
close(_sock);
_sock = -1;
}
private:
int _sock;
};

在这之外,上面的头文件中还有几个其他接口函数,需要了解

htons()作用是将端口号由主机字节序转换为网络字节序的整数值。(host to net)

inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值,用于sockaddr_in.sin_addr.s_addr。

数字的字节序转换:

​ 主机字节序转换为网络字节序

1
2
uint32_t	htonl(uint32_t	hostlong)
uint16_t htons(uint16_t hostshort)

​ 网络字节序转换为主机字节序

1
2
uint32_t	ntohl(uint32_t	hostlong)
uint16_t ntohs(uint16_t hostshort)

将点分十进制ip地址转换为网络字节序ip地址:

1
2
in_addr inet_addr(const char *cp);
int inet_pton(int af,const char *src,void *dst)

将网络字节序IP地址转换为点分十进制IP地址:

1
2
const char *inet_ntop(int af,const void* src,char *dst,socklen_t size);
char *inet_ntoa(struct in_addr in);
udp服务端
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
#include "udpsocket.hpp"    

#define CHECK_RET(q) if((q) == false){return -1;}

int main(int argc,char *argv[])
{
if(argc != 3)
{
cout<<"./udp_server ip port:"<<endl;
return -1;
}
string ip = argv[1];
uint16_t port = atoi(argv[2]);

UdpSocket sock;
CHECK_RET(sock.Socket());
CHECK_RET(sock.Bind(ip,port));

while(1)
{
string buf;
struct sockaddr_in cli_addr;
CHECK_RET(sock.Recv(buf,&cli_addr));
cout<<"client say:"<<buf<<endl;

cout<<"server say:";
fflush(stdout);
cin>>buf;
CHECK_RET(sock.Send(buf,&cli_addr));
}
sock.Close();
}
udp客户端
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
#include "udpsocket.hpp"    

#define CHECK_RET(q) if((q) == false){return -1;}

int main(int argc,char *argv[])
{
if(argc != 3)
{
cout<<"./udp_client ip port"<<endl;
return -1;
}
string ip = argv[1];
uint16_t port = atoi(argv[2]);

UdpSocket sock;
CHECK_RET(sock.Socket());

struct sockaddr_in srv_addr;
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(port);
srv_addr.sin_addr.s_addr = inet_addr(ip.c_str());

while(1)
{
string buf;
cout<<"client say:";
fflush(stdout);
cin>>buf;
CHECK_RET(sock.Send(buf,&srv_addr));
CHECK_RET(sock.Recv(buf,&srv_addr));
cout<<"server say"<<buf<<endl;
}
sock.Close();
}

网络1

两边聊天如上图

udp的传输速度快,无连接,不可靠,面向数据报这个必须牢牢记住了。

但是在代码中有个bug存在,发送的数据如果有空格,那么数据就会产生截断,分成两次发送

-------------The End-------------