SOCKET网络编程学习笔记

一、socket初识

socket可以看成是用户进程与内核网络协议栈的编程接口
socket不仅可以用于本机进程间的通信,还可以用于网络上不同主机的进程间通信。
在这里插入图片描述

二、通用地址结构

通用地址结构用来制定与套接字关联的地址

struct socka ddr{
unit8_t sin_len;
sa_family sin_family;
    char sa_data[14];
};

sin_len : 整个sockaddr结构体的长度
sin_family:指定该地址家族
sa_data: 由sin_family决定他的形式

三、网络字节序

在这里插入图片描述
在这里插入图片描述

字节序测试

#include<stdio.h>

int main(void)
{
  unsigned int x = 0x12345678;
  unsigned char *p = (unsigned char*)&x;
  printf("%0x,%0x,%0x,%0x\n",p[0],p[1],p[2],p[3]);
  return 0;
}

字节序转换函数

uint32_t htonl(uint32_t hostlong);uint16_t htons(uint16_t hostshort);
uint32_t ntonl(uint32_t netlong);
uint16_t ntons(uint16_t netshort);

在上述函数中,h代表host;n代表network;s代表short;l代表long。

四、套接字类型

  • 流式套接字(SOCKET_STREAM):提供面向连接的,可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接收。
  • 数据报式套接字(SOCKET_DGRAM):提供无连接服务。不提供无错保证,数据可能丢失或重复,并且接受顺序混乱。
  • 原始套接字(SOCKET_RAW)

五、TCP客户/服务器模型

在这里插入图片描述

TCP回射客户/服务器
在这里插入图片描述
TCP回射服务器

 //回射服务器


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




#define ERR_EXIT(m) \
		do\
		{\
			perror(m);\
			exit(EXIT_FAILURE);\
		}while(0)


int main(void)
{
	int listenfd;
	if((listenfd = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
		ERR_EXIT("socket");
	struct sockaddr_in servaddr;
	memset(&servaddr,0,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	int on = 1;
	if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) < 0)
		ERR_EXIT("setsockopt");

	if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
		ERR_EXIT("bind");

	if(listen(listenfd,SOMAXCONN) < 0)
		ERR_EXIT("listen");

	struct sockaddr_in peeraddr;
	socklen_t peerlen = sizeof(peeraddr);
	int conn;
	if((conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen)) < 0)
		ERR_EXIT("accept");

	//打印输出链接到的客户端的ip和端口
	printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));

	char recvbuf[1024];
	while(1)
	{
		memset(recvbuf,0,sizeof(recvbuf));
		int ret = read(conn,recvbuf,sizeof(recvbuf));
		fputs(recvbuf,stdout);
		write(conn,recvbuf,ret);
	}

	close(conn);
	close(listenfd);

	return 0;

}

TCP回射客户端

//回射客户端


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




#define ERR_EXIT(m) \
		do\
		{\
			perror(m);\
			exit(EXIT_FAILURE);\
		}while(0)


struct packet
{
	int len;
	char buf[1024];
};


ssize_t readn(int fd,void *buf,size_t count)
{
	size_t nleft = count;
	ssize_t nread;
	char *bufp = (char*)buf;

	while(nleft > 0)
	{
		if((nread = read(fd,bufp,nleft)) < 0){
			if(errno == EINTR){
				continue;
			}
			return -1;
		}else if(nread == 0){
			return count - nleft;
		}else{
			bufp += nread;
			nleft -= nread;
		}
		
	}

	return count;
}

ssize_t writen(int fd,const void *buf,size_t count)
{
	size_t nleft = count;
	ssize_t nwritten;
	char *bufp = (char*)buf;

	while(nleft > 0)
	{
		if((nwritten = write(fd,bufp,nleft)) < 0){
			if(errno == EINTR){
				continue;
			}
			return -1;
		}else if(nwritten == 0){
			continue;
		}else{
			bufp += nwritten;
			nleft -= nwritten;
		}
		
	}

	return count;
}


int main(void)
{
	int sock;
	if((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
		ERR_EXIT("socket");
	struct sockaddr_in servaddr;
	memset(&servaddr,0,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	
	if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
		ERR_EXIT("connect");

	struct packet sendbuf;
	struct packet recvbuf;
	memset(&sendbuf,0,sizeof(sendbuf));
	memset(&sendbuf,0,sizeof(recvbuf));


	// char sendbuf[1024] = {0};
	// char recvbuf[1024] = {0};


	int n ;

	while(fgets(sendbuf.buf,sizeof(sendbuf.buf),stdin) != NULL)
	{
		//通信细节
		n = strlen(sendbuf.buf);
		sendbuf.len = htonl(n);
		writen(sock,&sendbuf,4+n);

		int ret = readn(sock,&recvbuf.len,4);
		if(ret == -1)
		{
			ERR_EXIT("read");
		}
		else if (ret < 4)
		{
			printf("client_close\n");
			break;
		}else 
		{
			n = ntohl(recvbuf.len);
			ret = readn(sock,recvbuf.buf,n);
			if(ret == -1)
			{
				ERR_EXIT("read");
			}
			else if (ret < n)
			{
				printf("client_close\n");
				break;
			}else 
			{
				fputs(recvbuf.buf,stdout);
				memset(&sendbuf,0,sizeof(sendbuf));
				memset(&sendbuf,0,sizeof(recvbuf));
			}
			
		}

		


	}

	close(sock);
	

	return 0;

}

六、流协议与粘包

在这里插入图片描述

粘包的解决方案(本质上是要在应用层维护消息与消息的边界)

  • 定长包
  • 包尾加\r\n (ftp)
  • 包头加上包体长度
  • 更复杂的应用层协议

七、TCP的十一种状态

八、五种I/O模型

  1. 阻塞I/O在这里插入图片描述

  2. 非阻塞I/O在这里插入图片描述

  3. I/O复用(select和poll)在这里插入图片描述

  4. 信号驱动I/O在这里插入图片描述

  5. 异步I/O在这里插入图片描述

九、select管理者

用select来管理I/O,一旦其中一个I/O或者多个I/O检测到我们所感兴趣的事件,select函数返回,返回值未检测到的事件个数。并且返回了那些I/O发生了事件。遍历这些事件进而去处理他。

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

nfds : 读,写,异常集合中的文件描述符的最大值+1
readfds : 读集合
writefds : 写集合
exceptfds : 异常集合
timeout : 超时结构体

十、读、写、异常事件发生条件

  • 可读

套接口缓冲区有数据可读
连接的读一半关闭,即接收到了FIN段,读操作将返回0
如果是监听套接口,已完成连接队列不为空时。
套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取

  • 可写

套接口发送缓冲区有空间容乃数据
连接的写一半关闭。即收到RST段之后,再次调用write操作
套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROT选项来获取

  • 异常

套接口存在带外数据

十一、close和shutdown的区别

  • close终止了数据传送的两个方向,既不能往套接字中写入数据,也不能从套接字中读数据。
  • shutdown可以有选择的终止某个方向的数据传送或者终止数据传送的两个方向
  • shutdown
    how=1就可以保证对等方接收到一个EOF字符,而不管其他进程是否已经打开了套接字。而close不能保证,直到套接字引用计数减为0时才发送。也就是说直到所有的进程都关闭了套接字。

十二、套接字I/O超时设置方法

  1. alarm 闹钟
  2. 套接字选项 SO_SNDTIMEO SO_RCVTIMEO
  3. select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装
    connect_timeout函数封装

十三、并发与迭代

套接字编程经常使用在客户/服务器编程模型(简称C/S模型)中,C/S模型根据复杂度分为简单的客户/服务器模型和复杂的客户/服务器模型。
C/S简单客户/服务器模型是一对一关系,一个服务器端某一时间段内只对应处理一个客户端的请求,迭代服务器模型属于此模型。
C/S复杂服务器模型是一对多关系,一个服务器端某一时间段内对应处理多个客户端的请求,并发服务器模型属于此模型。

迭代服务器模型和并发服务器模型是socket编程中最常见使用的两种编程模型。
通常来说大多数TCP服务器是并发的,大多数UDP服务器是迭代的
比如我们通常在做聊天室的时候,使用UDP协议来发送消息,但是在用户需要上传下载数据的时候,我们通常在服务器和客户端之间建立一个TCP连接来传送文件。。。
但是如果服务一个客户请求的时间不长,使用迭代服务器没有太大问题,一旦客户请求的时间需要花费很长,不希望整个服务器被单个客户长期占用,而希望同时服务多个客户,就需要选择并发服务器了。
在这里插入图片描述

十四、select限制

用select实现的并发服务器,能达到的并发数,受两方面的限制
一个进程能打开的最大文件描述符限制。这可以通过调整内核参数
select中的fd_set集合容量限制(FD_SETSIZE),这需要重新编译内核。

十五、UDP

UDP特点
无连接
基于消息的数据传输服务
不可靠
一般情况下UDP更加高效

UDP客户/服务基本模型
在这里插入图片描述

UDP回射客户/服务器
在这里插入图片描述
UDP回射服务器

 //回射服务器


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




#define ERR_EXIT(m) \
		do\
		{\
			perror(m);\
			exit(EXIT_FAILURE);\
		}while(0)


void echo_srv(int sock)
{
	char recvbuf[1024] = {0};
	struct sockaddr_in peeraddr;
	socklen_t peerlen;
	int n;
	while(1)
	{
		peerlen = sizeof(peeraddr);
		memset(recvbuf,0,sizeof(recvbuf));
		n = recvfrom(sock,recvbuf,sizeof(recvbuf),0,(struct sockaddr*)&peeraddr,&peerlen);
		if (n == 1)
		{
			if (errno == EINTR)
			{
				continue;
			}
			else
			{
				ERR_EXIT("recvfrom");
			}
		}
		else if(n > 0)
		{
			fputs(recvbuf,stdout);
			sendto(sock,recvbuf,n,0,(struct sockaddr*)&peeraddr,peerlen);
		}
	}
	close(sock);
}

int main(void)
{
	int sock;
	if((sock = socket(PF_INET,SOCK_DGRAM,0)) < 0)
		ERR_EXIT("socket");

	struct sockaddr_in servaddr;
	memset(&servaddr,0,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
	{
		ERR_EXIT("bind");
	}

	echo_srv(sock);
	return 0;

}

UDP回射客户端

 //回射客户端


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




#define ERR_EXIT(m) \
		do\
		{\
			perror(m);\
			exit(EXIT_FAILURE);\
		}while(0)


void echo_cli(int sock)
{
	struct sockaddr_in servaddr;
	memset(&servaddr,0,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = inet_addr("127.0.01");

	char sendbuf[1024] = {0};
	char recvbuf[1024] = {0};
	while(fgets(sendbuf,sizeof(sendbuf),stdin) != NULL)
	{
		sendto(sock,sendbuf,strlen(sendbuf),0,(struct sockaddr*)&servaddr,sizeof(servaddr));
		recvfrom(sock,recvbuf,sizeof(recvbuf),0,NULL,NULL);
		fputs(recvbuf,stdout);
		memset(sendbuf,0,sizeof(sendbuf));
		memset(recvbuf,0,sizeof(recvbuf));
	}

	close(sock);

}

int main(void)
{
	int sock;
	if((sock = socket(PF_INET,SOCK_DGRAM,0)) < 0)
		ERR_EXIT("socket");

	echo_cli(sock);

	

	return 0;

}

UDP注意点
UDP报文可能会丢失重复
UDP报文可能会乱序
UDP缺乏流量控制
UDP协议数据报文截断
recvfrom返回0,不代表连接关闭,因为UDP是无连接的
ICMP异步错误
UDP connnect
UDP外出接口的确定

十六、UNIX域

UNIX域协议特点
UNIX域套接字与TCP套接字相比较,在同一台主机上传输速度前者是后者的两倍
UNIX域套接字可以在同一台主机上各进程之间传输描述符
UNIX域套接字与传统套接字的区别是用路径名来表示协议族的描述

UNIX域地址结构

#define UNIX_PATH_MAX 108

struct sockaddr_un{
sa_family_t  sun_family;    /*AF_UNIX*/
char  sun_path[UNIX_PATH_MAX];  /*pathname*/
}

UNIX域套接字编程注意点
bind成功将创建一个文件,权限为0777&umask
sun_path最好用一个绝对路径
UNIX域协议支持流式套接口与报式套接口
UNIX域流式套接字connect发现监听队列满时,会立刻返回一个ECONNREFUSED,这和TCP不同,如果监听队列满,会忽略到来的SYN,这导致对方重传SYN;

十七、socketpair

功能:创建一个全双工流管道
原型: intsocketpair(int domain, int type,int protocol,int sv[2]);
参数
domain : 协议家族
type: 套接字类型
protocol:协议类型
sv:返回套接字对
返回值:成功返回0,失败返回-1。

sendmsg/recvmsg两个方法的使用

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈游戏开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值