Linux网络编程 1.socket套接字

Linux网络编程 socket套接字

1.什么是socket套接字

​ Socket套接字由远景研究规划局(Advanced Research Projects Agency, ARPA)资助加里福尼亚大学伯克利分校的一个研究组研发。其目的是将TCP/IP协议相关软件移植到UNIX类系统中。设计者开发了一个接口,以便应用程序能简单地调用该接口通信。这个接口不断完善,最终形成了Socket套接字。Linux系统采用了Socket套接字,因此,Socket接口就被广泛使用,到现在已经成为事实上的标准。与套接字相关的函数被包含在头文件sys/socket.h中。

简单来说socket套接字就是一套TCP/IP通信协议的API。

套接字怎么使用呢?

套接字通信一般用于连接客户端和服务器。在服务器端:

​ 由于服务器时被动提供服务,客户端主动连接,所以服务器应该先于客户端启动,启动服务器之后服务器的IP和端口不能变化。服务器的ip和端口要在启动之前绑定。

在客户端:

​ 因为客户端是主动连接服务器,所以需要知道服务器的ip地址,和服务器主机上的服务器进程:端口。

到这我们先不着急看代码,还有一个重要问题需要解决:

首先我们想象一个通信的场景:

客户端给服务器发送了一个复杂的数据-结构体,在客户端数据的低字节存储在内存的低地址位,高字节存储在高地址位。而服务端恰好相反。这时候服务器接收的数据就会混乱。

2.字节序

在各种计算机体系结构中,对于字节、字等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元(比特、字节、字、双字等等)应该以什么样的顺序进行传送。如果不达成一致的规则,通信双方将无法进行正确的编/译码从而导致通信失败。

字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)

目前在各种体系的计算机中通常采用的字节存储机制主要有两种:Big-Endian 和 Little-Endian,下面先从字节序说起。

概念:

小端存储->主机字节序

存储方式:数据低字节存放在内存低地址位,高字节存放在高地址位(低低,高高)。我们的电脑都是按照小端存储在内存中存数据的。

大端存储->网络字节序

存储方式:数据的低字节存放在内存的高地址位,高字节存放在低地址位(低高,高低)。通信过程中使用的数据全部使用网络字节序。

举例:
// 有一个16位的数: 0x12345678   ->   这个数是 4 个字节	
// 这个数在内存中按照大端, 小端如何存储
// 内存低地址位 --------------> 内存的高地址位
小端:  0x78   0x56   0x34   0x12
大端:  0x12   0x34   0x56   0x78

相关函数:

BSD Socket提供了封装好的转换接口,方便程序员使用。包括从主机字节序到网络字节序的转换函数:htons、htonl;从网络字节序到主机字节序的转换函数:ntohs、ntohl。

函数看这里:
#include <arpa/inet.h>
// h -> host -> 主机字节序
// to -> 转换
// n -> net -> 网络字节序
// s -> short -> 16位整形数
// l -> long -> 32位整形数
// 参数: 要转换的数据
// 返回值: 转换完成之后得到的结果

//一般端口的转换:
//主机字节序->网络字节序
uint16_t htons(uint16_t hostshort);
//网络字节序转主机字节序
uint16_t htons(uint16_t netshort);

//一般对整形IP进行转换
//主机字节序->网络字节序
uint32_t htonl(uint32_t hostlong);
//网络字节序->主机字节序
uint32_t ntohl(uint32_t netlong);



点分十进制IP地址转换

#include <arpa/inet.h>
// p -> 主机字节序的点分十进制IP地址(字符串): 192.168.1.100
// n -> 网络字节序的整形IP地址
// 将本地IP字符串 -> 整形大端IP地址
int inet_pton(int af,const char* src,void *dst);
参数:
-af:地址族协议 ipv4:AF_INET, ipv6:AF_INET6
-src: 要被转换的点分十进制的IP地址: 192.168.1.100这种格式
- dst: 转换得到的大端IP存储到这个指针指向的内存中
	返回值:
		转换成功: 0
           失败: -1

// 整形大端IP地址  -> 将本地IP字符串         
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
	参数: 
		- af: 地址族协议, 使用的ipv4还是ipv6的ip地址
			- ipv4: AF_INET
			- ipv6: AF_INET6
		- src: 这个指针指向的内存中存储了大端的整形IP地址
		- dst: 存储得到的IP地址字符串
		- size: dst参数指向的内存的总大小(容量)
    返回值:
		成功: 返回第三个参数dst的地址
		失败: NULL               
               

3.sockaddr数据结构

说完了字节序,我们来讨论一下用于存放地址信息的结构体: sockaddr

如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r5Mv2QP7-1576504082585)(assets/sockaddr.png)]

//结构不算难理解,后面主要会用到sockaddr_in和sockaddr
struct sockaddr {
	sa_family_t sa_family;	// 地址族协议, ipv4, ipv6
	char        sa_data[14];
}

struct in_addr
{
    in_addr_t s_addr;
};  
struct sockaddr_in
{
    sa_family_t sin_family;		/* __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)];
};  

4.套接字函数

#include <arpa/inet.h>
// 创建套接字
int socket(int domain, int type, int protocol);
	参数:
		- domain: 指定地址族协议
			- AF_UNIX, AF_LOCAL: 用于本地进程间通信(本地套接字)
            - AF_INET: 使用IPv4
            - AF_INET6: 使用IPv6
        - type: 使用什么传输协议
        	- SOCK_STREAM: 流式传输协议
        	- SOCK_DGRAM: 报式传输协议
        - protocol: 指定具体的协议, 这个参数一般指定为0
        	- 使用流式协议中的tcp协议
        	- 使用报式协议中的udp协议
	返回值:
		成功: 返回用于socket的文件描述符
		失败: -1

// 将监听的套接字和本地的IP和端口绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	参数:
		- sockfd: 用于监听的套接字
		- addr: 在这个变量中初始化要绑定的IP和端口信息
		- addrlen: 第二个参数addr对应的内存大小
	返回值:
		成功: 0
        失败: -1

// 设置监听->给监听的套接字
int listen(int sockfd, int backlog);
	参数:
		- sockfd: 绑定成功的套接字
		- backlog: 可以最大同时监听多少个客户端连接
			- 这个值最大是128, 如果参数值超过128, 还是按128处理
	返回值:
		成功: 0
        失败: -1
            
// 等待并接受客户端连接, 阻塞函数
// 开始监听之后, 没有客户端连接服务器, 这时候程序就阻塞在了accept函数上
// 当检测到了客户端连接, 这个函数解除阻塞, 和客户端建立连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	参数:
		- sockfd: 用户监听的套接字
		- addr: 传出参数, 得到连接的客户端的IP和端口信息
		- addrlen: 传入传出参数, 记录了参数addr指针对应的内存大小
	返回值:
		成功: 通信的文件描述符
		失败: -1

// 接收数据
ssize_t read(int fd, void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
	参数:
		- 前三个参数和read一样
		- flags: 套接字是一些属性, 默认使用0
// 发送数据
ssize_t write(int fd, const void *buf, size_t count);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
	参数:
		- 前三个参数和write一样
		- flags: 套接字是一些属性, 默认使用0
		
// 连接服务器, 这个函数也是阻塞函数
// 连接完成之后, 解除阻塞
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	参数:
		- sockfd: 客户端用于通信的套接字
		- addr: 初始化服务器的IP和端口, 通过这个地址连接服务器
		- addrlen: 参数 addr 指针指向的内存大小, sizeof(addr)
   返回值:
		成功: 0
        失败: -1

通信代码如下:

//server端
#include <cstdio>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>


int main()
{
    //1.创建套接字
	int mfd = socket(AF_INET, SOCK_STREAM, 0);
	if (mfd<0)
	{
		perror("socket");
		exit(0);
	}
	//2.绑定IP和端口
	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_port = htons(8888);//本机字节序转网络字节序 (转为大端)
	server.sin_addr.s_addr = INADDR_ANY;//使用这个宏绑定网卡所有ip

	int ret = bind(mfd, (sockaddr*)& server, sizeof(server));
	if (ret<0)
	{
		perror("bind");
		exit(0);
	}

	//3.监听
	ret = listen(mfd, 100);
	if (ret<0)
	{
		perror("lfd");
		exit(0);
	}

	//4.等待连接
	//连接后获得通信文件描述符lfd,对应信息存放在sockaddr_in这个结构体中
	struct sockaddr_in conaddr;
	int len = sizeof(conaddr);
	int lfd = accept(mfd, (struct sockaddr*)&conaddr, &len);
	if (lfd<0)
	{
		perror("accept");
		exit(0);
	}

	//5.通信
	while (1)
	{
		char buf[100] = { 0 };
		int count = recv(lfd, buf, sizeof(buf), 0);
		printf("在服务器端打印%s\n", buf);
		//发送数据,接收什么发送什么
		send(lfd, buf, count, 0);
	}

	close(mfd);
	close(lfd);

    return 0;
}
//client端
#include<stdlib.h>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<iostream>

int main()
{
	//1.创建套接字
	int mfd = socket(AF_INET, SOCK_STREAM, 0);
	if (mfd<0)
	{
		perror("socket");
		exit(0);
	}

	//2.连接服务器使用服务器绑定的IP和端口
	struct sockaddr_in client;
	client.sin_family = AF_INET;
	client.sin_port = htonl(8888);
	//点分十进制转大端整形
	inet_pton(AF_INET, "192.168.xxx.xxx",&client.sin_addr.s_addr);
	int ret = connect(mfd, (sockaddr*)& client, sizeof(client));
	if (ret<0)
	{
		perror("connect");
		exit(0);
	}
	//3.通信
	while (1)
	{
		char buf[1024] = { 0 };
		std::cin >> buf;
		send(mfd, buf, sizeof(buf), 0);//发送数据

		//接收数据
		char rebuf[1024] = { 0 };
		int ret=recv(mfd, rebuf, sizeof(rebuf), 0);
		if (ret==0)
		{
			printf("server disconnect...\n");
			break;
		}
		printf("from server:%s\n", rebuf);
		sleep(1);
	}
	close(mfd);
	return 0;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值