016 socket API编程 TCP通讯

1. 套接字的概念

Socket ,在Linux环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。

区别与管道主要应用于本地进程间通信,套接字多应用于网络进程间数据的传递!!!


在TCP/IP协议中,IP地址(32位)可在网络环境中,唯一标识一台主机;端口号(16位, 上限2^16 = 65536)可在一台主机上,唯一标识一个进程。所以,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程!!!!

“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系

在这里插入图片描述

在网络通信中,套接字一定是成对出现的。


TCP/IP协议最早在BSD UNIX上实现,为TCP/IP协议设计的应用层编程接口称为socket API。

在这里插入图片描述

2. 网络字节序

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。

一般主机采用小端存储,所以两者通信要做字节序的转换

大端:低地址存放高字节数(网络存储)

小端:低地址存放低字节数(PC本地存储)

2.1 网络字节序和主机字节序的库函数转换

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);			// 本地->网络(32位, IP地址)
uint16_t htons(uint16_t hostshort);			// 本都->网络(16位, 端口号)
uint32_t ntohl(uint32_t netlong);			// 网络->端口(32位, IP地址)
uint16_t ntohs(uint16_t netshort);			// 网络->端口(16位, 端口号)

h表示host,n表示network,l表示32位长整数,s表示16位短整数。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

注意:日常使用IP地址(128.168.188.120)是点分十进制表示,本质是 string 字符串,需要调用 atoi 函数转为 int 二进制后,才能调用 htonl 进行字节序转换

2.2 IP地址转换函数

点分十进制转二进制,本地字节序(string IP) -> 网络字节序

man inet_pton		// convert IPv4 and IPv6 addresses from text to binary form
#include <arpa/inet.h>

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

参数说明:
	af:AF_INET、AF_INET6(分别代表IPV4、IPV6)
	src:传入,IP地址(点分十进制)
	dst:传出,转换后的 网络字节序的 IP地址

返回值:

​ 成功: 1

​ 异常: 0, 说明 src 指向的不是一个有效的 ip 地址

​ 失败:-1


二进制转点分十进制,网络字节序 -> 本地字节序(string IP)

man inet_ntop					// convert IPv4 and IPv6 addresses from binary to text form
#include <arpa/inet.h>

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

参数说明:
		af:AF_INET、AF_INET6
		src: 网络字节序IP地址
		dst:本地字节序(string IP)
		size: dst 的大小。

返回值:

​ 成功:dst

​ 失败:NULL

2.3 sockaddr 数据结构

IP + port : 可在网络环境中唯一标识一个进程

man 7 ip

struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */
               in_port_t      sin_port;   /* port in network byte order */
               struct in_addr sin_addr;   /* internet address */
           };
           
struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };

初始化结构体的demo:

	struct sockaddr_in addr;

	addr.sin_family = AF_INET/AF_INET6				man 7 ip

	addr.sin_port = htons(9527);
	
	int dst;
	inet_pton(AF_INET, "192.157.22.45", (void *)&dst);
	addr.sin_addr.s_addr = dst;
	// addr.sin_addr.s_addr = htonl(INADDR_ANY);		取出系统中有效的任意IP地址。二进制类型。

因历史发展原因,为了向前兼容,在不同函数调用中需做参数类型的强制转换

3. 网络套接字函数

3.1 socket模型创建流程图

3.2 socket 函数

#include <sys/socket.h>

int socket(int domain, int type, int protocol);		// 创建一个 套接字

参数说明:
		domain:AF_INET、AF_INET6、AF_UNIX
		type:SOCK_STREAM、SOCK_DGRAM
		protocol: 0 

返回值:

​    成功:新套接字所对应文件描述符

​    失败:-1,errno

3.3 bind 函数

给socket绑定一个 地址结构 (IP+port)

#include <arpa/inet.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数说明:
		sockfd: socket 函数返回值
		addr: 传入参数(struct sockaddr *)&addr
				struct sockaddr_in addr;
				addr.sin_family = AF_INET;
				addr.sin_port = htons(8888);
				addr.sin_addr.s_addr = htonl(INADDR_ANY);
		addrlen: sizeof(addr) 地址结构的大小

返回值:

​    成功:0

​    失败:-1 errno

3.4 listen 函数

设置同时与服务器建立连接的上限数。(同时进行3次握手的客户端数量)

#include <sys/types.h> 
#include <sys/socket.h>

int listen(int sockfd, int backlog);

参数说明:
		sockfd: socket 函数返回值
		backlog:上限数值。最大值 128.

返回值:

​    成功:0

​    失败:-1 errno

3.5 accept 函数

阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符。

#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数说明:
		sockfd: socket 函数返回值
		addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(IP+port)
		addrlen:传入传出。 &clit_addr_len
				socklen_t clit_addr_len = sizeof(addr);
		入:addr的大小。 出:客户端addr实际大小。

返回值:

​    成功:能与客户端进行数据通信的 socket 对应的文件描述。

​    失败: -1 , errno

3.6 connect 函数

使用现有的 socket 与服务器建立连接

initiate a connection on a socket

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数说明:

		sockfd: socket 函数返回值
		addr:传入参数。服务器的地址结构
				struct sockaddr_in srv_addr;		// 服务器地址结构
				srv_addr.sin_family = AF_INET;
				srv_addr.sin_port = 9527 	跟服务器bind时设定的 port 完全一致。
				inet_pton(AF_INET, "服务器的IP地址",&srv_adrr.sin_addr.s_addr);
		addr:传入参数。服务器的地址结构
		addrlen:服务器的地址结构的大小

返回值:

​    成功:0

​    失败:-1 errno

如果不使用bind绑定客户端地址结构, 采用"隐式绑定".

4. socket API demo

4.1 TCP通信流程分析:

server:

  1. socket() 创建socket

  2. bind() 绑定服务器地址结构

  3. listen() 设置监听上限

  4. accept() 阻塞监听客户端连接

  5. read(fd) 读socket获取客户端数据

  6. 小–大写 toupper()

  7. write(fd)

  8. close();


client:

  1. socket() 创建socket

  2. connect(); 与服务器建立连接

  3. write() 写数据到 socket

  4. read() 读转换后的数据。

  5. 显示读取结果

  6. close()

4.2 server demo 服务器端

/* server.c */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<ctype.h>
#include<sys//socket.h>
#include<arpa/inet.h>

#define SERV_PORT 9537

void sys_err(const char* str)
{
	perror(str);
	exit(1);
}

int main(int argc, char * argv [ ])
{
	int lfd, cfd ;
	int ret, i;
	char buf[BUFSIZ], client_IP[1024];
	
	struct sockaddr_in serv_addr, clit_addr;
	socklen_t clit_addr_len;

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(SERV_PORT);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	lfd = socket(AF_INET, SOCK_STREAM, 0);

	bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

	listen(lfd, 128);

	clit_addr_len = sizeof(clit_addr);

	cfd = accept(lfd, (struct sockaddr*)&clit_addr, &clit_addr_len);

	printf("client ip:%s port:%d\n",
		inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, client_IP, sizeof(client_IP)),
		ntohs(clit_addr.sin_port));

	while(1)
	{
		ret = read(cfd, buf, sizeof(buf));
		write(STDOUT_FILENO, buf, ret);

		for (i = 0; i < ret; i++)
			buf[i] = toupper(buf[i]);

		write(cfd, buf, ret);
	}

	close(lfd);
	close(cfd);
	
	return 0;
}

注意: 实际工程中,切记一定检查函数返回值

nc 192.168.188.100 9537     // 与服务器进行通信

client demo 客户端

/* client.c */
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<sys/socket.h>
#include<arpa/inet.h>

#define SERV_PORT 9537

int main(int argc, char * argv [])
{
	int cfd, ret;
	int conter = 10;
	char buf[BUFSIZ];

	struct sockaddr_in serv_addr;

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(SERV_PORT);
	inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);

	cfd = socket(AF_INET, SOCK_STREAM, 0);

	connect(cfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

	while(--conter)
	{
		write(cfd, "hello\n", 6);
		ret = read(cfd, buf, sizeof(buf));
		write(STDOUT_FILENO,buf, ret);
		sleep(1);
	}

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值