TCP socket基础套接字

基础TCP套接字函数

一、套接口通信模型

​ 主要函数如下图所示:

在这里插入图片描述

​ 在这个模型中,左侧为TCP客户端连接的模型,先创建套接口,再通过connect()主动连接服务端。右侧为服务器端,服务器端需要侦听来自客户的连接并进行三路连接建立全双工的一个完整连接,所以需要先建立listen()套接口,监听到的连接再通过accept()建立可读写的套接口。

​ 在linux中,套接口也是一个文件,这个文件就是一个生产者消费者模型。但是我们的关注点不在于缓冲区是否满(因为这些由内核协议栈关注),而我们关注的是数据和连接。可以看出服务器获取连接的服务端模型如下。

while(1)
{
	int clientfd = accept(listenfd,(struct sockaddr*)&clientaddr,&clientlen);	//服务端套接字
   	//对于客户端连接我们应该怎么管理?数据怎么处理?
}

​ 所谓的高并发就是在多线程和多进程中把连接以及数据进行合理的管理,其次一个高性能服务器还需要有许多基本的组件:定时器、日志、数据处理程序等,不过这些都是后话了。

​ 在上图中还可以看到,建立连接后就是对于这个‘文件’进行读写了,然后处理数据,返回数据。这就是基本的C/S模型。接下来逐一介绍这些基本套接口。

二、基本套接口介绍

1、socket()

​ #include <sys/socket.h>

​ int socket(int family, int type, int product);

​ 想要执行一次网络IO,最基础的就是创建一个文件描述符进行读写操作。和常规文件不同,“网络文件”的套接字需要一个特殊的函数来申请——socket。

​ 函数中的第一个参数family,指定所属协议族(如IPv4,IPv6);第二个参数是所属网络层协议,TCP是SOCK_STREAM(数据流协议),UDP是SOCK_DGRAM(数据报协议);第三个参数是某个协议类型常值,一般设为0,表示默认值(默认值由系统自己判断)。

​ 这个函数的功效就是返回一个文件描述符,这个描述符和普通文件看似无异,可以使用fcntl进行修改。

​ 显然单独的sockfd没有用,请看下面代码段

#include <stdio.h>	
#include "util.h"	//常用网络编程函数
int main()
{
    char buf[100];
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    write(sockfd,"helloworld",100);
    read(sockfd,buf,100);
    printf("%s\n",buf);
}

//输出为空
yqm@yqm-virtual-machine:~/文档/MyCode/UNP$ gcc test.c -o test
yqm@yqm-virtual-machine:~/文档/MyCode/UNP$ ./test
yqm@yqm-virtual-machine:~/文档/MyCode/UNP$ 

​ 说明此时sockfd不可用,为什么呢?因为没有和TCP客端建立起有效连接。而且在网络中表识一个对等方的4元组没有成型。下面就是需要来建立客端和服务端自己的套接口地址。

2、结构体 sockaddr_in 和 sockaddr

sockaddr_in是IPv4套接口地址。格式如下:

struct sockaddr_in{
	uint8_t	sin_len;		//结构长度
    sa_family_t	sin_family;	//所属协议族
    in_port_t	sin_port;	//端口
    
    struct in_addr	sin_addr;	//IPv4地址
    char	sin_zero[8];		//未使用的,留待以后使用
};

sockaddr是通用套接口地址。格式如下:

struct sockaddr{
	uint8_t		sa_len;
    sa_family_t	 a_family;
    char		sa_data[14];
};

地址里面需要初始化的只有三个参数:源地址、源端口号、协议族。

初始时需要注意字节序问题:源主机可能和目标主机的字节序不同,所以统一转换为网络字节序。相关函数有许多,如:

#include <netinet/in.h>
uint16_t htons(uint16_t host); //host to net
uint32_t htonl(uint32_t host);

uint16_t ntohs(uint16_t host); //net to host
uint32_t ntohs(uint32_t host);

#include <arpa/inet.h>

int inet_aton(const char *strptr, struct in_addr *addrptr);
char *inet_ntoa(struct in_addr inaddr);

int inet_potn(int family, const char *strptr, void *addrptr);
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);

这个时候就可以初始化套接字地址了:

#include <stdio.h>
#include "util.h"
int main()
{
    char buf[100];
    int sockfd = socket(AF_INET,SOCK_STREAM,0);

    //设置套接字地址
    struct sockaddr_in  addr;   bzero(&addr,sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    inet_pton(AF_INET,"127.0.0.1",addr.sin_addr.s_addr);

    //printf("%s\n",buf);
}

那么有了地址,就需要让地址和套接口描述符绑定了。

3、bind()

绑定套接口描述符和套接口地址

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

4、listen()和accept()

listen是一个服务端函数,来创建一个监听套接口,通过监听套接口配合accept获取客户套接口

函数原型如下:

int listen (int sockfd, int backlog);

​ 第一个参数是监听套接字(这个套接字需要是完整的,绑定过地址),第二个是队列长度。backlog作为队列长度,其实有争议,因为listen中的客端连接有两种:一、未完成连接。二、已完成连接。

​ 未完成三次握手的是未完成连接,完成三次握手的是已完成连接。unix手册上比较暧昧的地方在于此,未明确的定义未完成连接的定义。但是我们可以知道一定不能把bocklog设置为0。而且服务器越繁忙,队列长度就需要越大。因为listen队列算是一个缓冲队列。

accept是一个服务端函数,用来获取一个已建立连接的套接口。

函数原型:

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

​ 传入的后两个参数是服务端地址和长度。有什么用处?算是通过参数返回值。所以我们需要传入参数。在函数内修改位客端地址。第一个参数就是监听套接字,从listenfd的监听队列中取出一个元素,并返回这个连接的套接口。

5、connect()

客户端函数,发出一个服务端连接请求。

int connect(int sockfd, sockaddr* servaddr,socklen_t *len);

​ 第一个参数是服务端套接口,第二个参数是服务端套接口地址,第三个参数是套接口地址长度。

建立与服务器的连接,失败返回-1。

三、一个简易的C/S程序

客端

#include "util.h"


int main()
{
	struct sockaddr_in servaddr;

	//设置服务端套接口地址
	servaddr.sin_port = htons(PORT);	//端口号
	inet_pton(AF_INET,IP,&servaddr.sin_addr);
	servaddr.sin_family = AF_INET;


	int sockfd = socket(AF_INET,SOCK_STREAM,0);	//TCP套接口

	//有了服务器地址,就自动进行三路握手
	if(0>connect(sockfd,(sockaddr*)&servaddr,sizeof(servaddr)))
	{
		printf("connect errno\n");
	}	
	//此时连接已经建立

	char buf[]="hello world";

	if(sizeof(buf) != write(sockfd,buf,sizeof(buf)))
	{
		printf("写错误\n");
	}
	
}

服务端

#include "./util.h"


int main()
{
	char buf[4096];
	//客户端套接字地址
	struct sockaddr_in      servaddr,cliaddr;
    	bzero(&servaddr,sizeof(servaddr));
	inet_pton(AF_INET,IP,&servaddr.sin_addr); 
	servaddr.sin_port = htons(PORT);		//端口号 8888
	servaddr.sin_family = AF_INET;			  //ipv4
	//创建监听套接口
	int listenfd  = socket(AF_INET,SOCK_STREAM,0);	//选择TCP协议
	
    	//绑定本地套接口和套接口地址
	if(0>bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)))
	{
		perror("bind errno!\n");
	}

	if(0>listen(listenfd,5))
	{
		perror("listen errno\n");
	}

	//轮询listenfd
	while(1)
	{	
		socklen_t clilen;
		int fd = accept(listenfd,(sockaddr*)&cliaddr,&clilen);	
		int nbits;
		while(0 !=(nbits =  read(fd,buf,100)))
		{
			printf("已读 %d 字节\n",nbits);
		}
		printf("读到的数据为:%s\n",buf);
		close(fd);
	}
	close(listenfd);
}

这是最简单的模型,后续会新的模型,包括更多的接口,如:select、poll、epoll、mmap、timefd等。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

绿色健康老清新

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

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

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

打赏作者

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

抵扣说明:

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

余额充值