当前位置:   article > 正文

Linux网络编程(二)Socket编程

Linux网络编程(二)Socket编程

Socket编程

一、网络套接字概念:socket

一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现。)

在通信过程中, 套接字一定是成对出现的。
  • 1
  • 2
  • 3

套接字通讯原理

二、网络字节序和主机字节序的转换函数(ip和端口)

小端法:(pc本地存储、Intel架构)	高位存高地址。地位存低地址。	int a = 0x12345678

大端法:(网络存储、IDM公司)	高位存低地址。地位存高地址。

#include <arpa/inet.h>

	htonl --> 本地(host)--》网络(net) (IP)			192.168.1.11 --> string --> atoi --> int --> htonl --> 网络字节序

	htons --> 本地--》网络 (port)

	ntohl --> 网络--》 本地(IP)

	ntohs --> 网络--》 本地(Port)
	
	h-->host;n-->network;l-->32位长整数; s-->16位短整数
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

三、IP地址转换函数(inet_pton):

int inet_pton(int af, const char *src, void *dst);		本地字节序(string IP) ---> 网络字节序

	af:AF_INET、AF_INET6

	src:传入,IP地址(点分十进制)

	dst:传出,转换后的 网络字节序的 IP地址。 

	返回值:

		成功: 1

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

		失败:-1

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);	网络字节序(二进制) ---> 本地字节序(string IP)

	af:AF_INET、AF_INET6

	src: 网络字节序IP地址

	dst:本地字节序(string IP)--缓冲区

	size: dst 的大小。缓冲区大小

	返回值: 成功:dst。 	

		失败:NULL
  • 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

四、sockaddr地址结构: IP + port --> 在网络环境中唯一标识一个进程。

sockaddr数据结构

man 7 ip命令

man_7_ip

//定义
struct sockaddr_in addr;      // #include<arpa/inet.h>

//初始化
addr.sin_family = AF_INET/AF_INET6				
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);		INADDR_ANY取出本系统中有效的任意IP地址,二进制类型。转为网络字节序

//使用
bind(fd, (struct sockaddr *)&addr, size);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

五、socket函数:

socketApi

socket连续

#include <sys/socket.h>

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

	domain:AF_INET、AF_INET6、AF_UNIX

	type:SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)

	protocol: 0 

	返回值:

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

		失败: -1 errno 可使用perror();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

 #include <arpa/inet.h>

 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);		给socket绑定一个 地址结构 (IP+port)

	sockfd: socket 函数返回值

		struct sockaddr_in addr;

		addr.sin_family = AF_INET;

		addr.sin_port = htons(8888);

		addr.sin_addr.s_addr = htonl(INADDR_ANY);

	addr: 传入参数(struct sockaddr *)&addr

	addrlen: sizeof(addr) 地址结构的大小。

	返回值:

		成功:0

		失败:-1 errno
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

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

	sockfd: socket 函数返回值

	backlog:上限数值。最大值 128.


	返回值:

		成功:0

		失败:-1 errno	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);	阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符。
第2卷---系统函数
	sockfd: socket 函数返回值

	addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(IP+port)

		socklen_t clit_addr_len = sizeof(addr);

	addrlen:传入传出。 &clit_addr_len

		 入:addr的大小。 出:客户端addr实际大小。

	返回值:

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

		失败: -1 , errno
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);	  使用现有的 socket 与服务器建立连接

	sockfd: socket 函数返回值

		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绑定客户端地址结构, 采用"隐式绑定".
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

六、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. 从终端读取数据

	4. write()	写数据到 socket

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

	6. 显示读取结果

	7. close()
  • 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

示例:TCP通信完成客户端和服务端

//server.c

#include <arpa/inet.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

#define SERV_PORT 54321

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

int main(int argc, char *argv[])
{
	int ret = 0;
	int lfd = 0, cfd = 0;
	char buf[BUFSIZ], clit_ip[BUFSIZ];//BUFSIZ 表示4096

	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);	//创建套接字
	if(lfd == -1)
	{
		sys_error("socket error");	
	}	

	ret = bind(lfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr));//绑定本地ip和端口
	if(ret == -1)
	{
		sys_error("bind error");
	}

	ret = listen(lfd,30);//设置套接字的最大连接数
	if(ret == -1)
	{
		sys_error("listen error");
	}	

	clit_addr_len = sizeof(clit_addr);
	cfd = accept(lfd,(struct sockaddr *)&clit_addr, &clit_addr_len);//设置阻塞监听,返回连接socket描述符
	if(cfd == -1)
	{
		sys_error("accept error");
	}
	printf("client ip:%s,port:%d\n",//打印客户端ip和port
		inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,clit_ip,sizeof(clit_ip)),
		ntohs(clit_addr.sin_port)
		);
	

	while(1)
	{
		ret = read(cfd, buf, sizeof(buf));
		write(STDOUT_FILENO, buf, ret);
		if(ret == -1)
		{
			sys_error("read error");
		}
		for(int i=0; i<ret; i++)
			buf[i] = toupper(buf[i]);
		write(cfd, buf, ret);
	}

	close(lfd);
	close(cfd);

	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
  • 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
///client.c
#include <stdio.h> 
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define SERV_PORT 54321

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

int main(int argc, char* argv[])
{
	int cfd = 0, ret = 0;
	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);	
	if(cfd == -1)
	{
		sys_err("socket error");
	}

	//连接
	ret = connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));	
	if(ret == -1)
	{
		sys_err("connect error");
	}
	while(1)	
	{
		ret = read(STDIN_FILENO, buf, sizeof(buf));
		//写
		ret = write(cfd,buf,ret);	
		if(ret == -1)
		{
			sys_err("wirte error");
		}
		ret = read(cfd, buf, sizeof(buf));
		write(STDOUT_FILENO, buf, ret);	
	}
	close(cfd);
	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
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

七、错误处理函数:

封装目的: 

	在 server.c 编程过程中突出逻辑,将出错处理与逻辑分开,可以直接跳转man手册。


【wrap.c】								【wrap.h】


存放网络通信相关常用 自定义函数						存放 网络通信相关常用 自定义函数原型(声明)。

命名方式:系统调用函数首字符大写, 方便查看man手册
	
	  如:Listen()、Accept();

函数功能:调用系统调用函数,处理出错场景。

在 server.c 和 client.c 中调用 自定义函数

联合编译 server.c 和 wrap.c 生成 server

	 client.c 和 wrap.c 生成 client
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

readn:
	读 N 个字节
	
readline:
	读一行

read 函数的返回值:

	1. > 0 实际读到的字节数

	2. = 0 已经读到结尾(对端已经关闭)【 !重 !点 !】

	3. -1 应进一步判断errno的值:

		errno = EAGAIN or EWOULDBLOCK: 设置了非阻塞方式 读。 没有数据到达。 

		errno = EINTR 慢速系统调用被 中断。

		errno = “其他情况” 异常。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/735444
推荐阅读
相关标签
  

闽ICP备14008679号