linux网络编程学习

TCP协议

简略讲一下工作方式
1.建立连接需要三次握手
2.终止连接要四次握手

建立连接

第一次握手:client向server发SYN,然后进入SYN_SEND状态
(SYN = synchronous 同步的意思)
第二次握手:sever收到之后,会回一个SYN+ACK给client,然后自己进入SYN_RECV状态(synchronous receive)
第三次握手:client接收到之后,再回一个ACK给server,然后就进入Established状态(ACK = acknowledge (告知已收到))

三次握手完成之后就可以开始传输数据啦。

终止连接

第一次握手:可以是client或者是server任意一方提出,然后就会给另一方发送FIN信号
第二次握手:对方收到FIN之后会回一个ACK给对方
第三次握手:收到ACK的一方会回一个FIN给对方
第四次握手:收到FIN的一方会回一个ACK给对方

然后就终止连接啦

TCP(transmission control protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

具体可以在百度百科查看。

实际写代码上一般都唔会接触到这些内层的步骤,但是知道一下还是有必要的。

相关API

socket

socket叫套接字。在课程学习的时候讲到应用程序跟TCP协议之间就需要用到socket这个API来进行操作。用起来有点像open函数。

DESCRIPTION
socket() creates an endpoint for communication and returns a file descriptor that refers to that endpoint. The file descriptor returned by a successful call will be the lowest numbered file descriptor not currently open for the process.

socket会为通信创造一个端点,并且会返回一个文件描述符。
什么样的文件描述符?
refer to that endpoint :引用该端点的文件描述符。
然后就像open函数一样,fd是默认分配最小的。

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
       int socket(int domain, int type, int protocol);

看到socket这个API要传入三个参数,domian、type和protocol

1.domain 域
这个domain参数是用来指定一个通讯域的,在man手册里面可以先到详细。

	   Name                Purpose                          Man page
       AF_UNIX, AF_LOCAL   Local communication              unix(7)
       AF_INET             IPv4 Internet protocols          ip(7)
       AF_INET6            IPv6 Internet protocols          ipv6(7)
       AF_IPX              IPX - Novell protocols
       AF_NETLINK          Kernel user interface device     netlink(7)
       AF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)
       AF_AX25             Amateur radio AX.25 protocol
       AF_ATMPVC           Access to raw ATM PVCs
       AF_APPLETALK        AppleTalk                        ddp(7)
       AF_PACKET           Low level packet interface       packet(7)
       AF_ALG              Interface to kernel crypto API

比如用ipv4的,选AF_INET

2.type 用来指定通信semantic(语义)
man手册里面也讲到有好多种选择
注意一下:
SOCK_STREAM 是TCP协议用的
SOCK_DGRAM 是UDP协议的。

3.protocol — 协议
这个要传进去的protocol参数是一个数字编号来的。
man手册里面讲到

Normally only a single protocol exists to support a particular socket type 
within a given  protocol  family,  in  which  case  protocol can be specified as 0. 

这么说通常我们只需要设置为0就行了,只用一个协议嘛。至于怎么进行深入设置的,可以看protocols或者getprotoent。

bind

bind - 捆绑、约束

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

一个socket创建之后呢,它需要一个地址去指派或者说是挂钩起来的。所以需要用bind来给这个sockfd捆绑一个地址
const struct sockaddr *addr是个输入型参数;
socklen_t addrlen指定地址长度,单位是byte。

struct sockaddr *addr 这个结构体指针是啥呢?

可以在**/usr/include/netinet/in.h**里面找到这两个结构体
struct sockaddr_in 这个应该是IPV4用的
struct sockaddr_in6 这个应该是iIPV6用的
下面是struct sockaddr_in 的详细代码

/* Structure describing an Internet socket address.  */
struct sockaddr_in
  {
    __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)];
  };

结构体里面
in_port_t类型的定义为

/* Type to represent a port.  */
typedef uint16_t in_port_t;

struct in_addr定义为

/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
  {
    in_addr_t s_addr;
  };

把这些记录下来的原因其实不是要说很仔细的研究,就是为了有根可循。

listen

linsten API用于监测监听即将要到来的连接请求。(翻译得有点不好。)

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int listen(int sockfd, int backlog);

传入的第一个参数sockfd就是通过socket API得到的fd,第二个参数backlog用来规定排队的长度的。

The backlog argument defines the maximum length to which the queue of
pending connections for sockfd may grow.

connect

connect的作用是开始通信。

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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

里面的参数上述的一些API都有讲述,就不赘述了。

一些辅助的库函数

在man手册里面直接man inet就有这些相关的库函数
inet_aton、inet_ntoa、inetaddr

inet_aton

inet_aton的作用是把IPv4的数字点标识的地址转变成二进制(不过我们printf出来看的时候用的是%x十六进制打印出来看。)

int inet_aton(const char *cp, struct in_addr *inp);

const char *cp 传进去的就是你的ip地址
stuct in_addr *inp 到时候得到转换后的值就会存在inp结构体指针变量指向的结构体里面
下面是测试代码:

//inet_aton的使用
	//在IPv4模式下使用,number-and-dot 转换到 binary 形式
	//返回值是非0,如果是0则地址无效。
	//转换后的地址是存在inp指针指向的一个结构体里面的。
	char cp[50] = IP_ADDR;
	struct in_addr inp;
	
	int ret = 0;
	ret = inet_aton(cp,&inp);
	if(0 == ret)
		{
			printf("the address is not valid\n");
			return -1;
		} 
	
		printf("the convert value is %x\n",inp.s_addr);
		printf("\n↓********test of inet_ntoa**********\n");
		printf("the convert value is %s\n",inet_ntoa(inp));

inet_ntoa

作用跟inet_aton相反,倒过来转换的,看inet_aton的测试代码的时候可以看到最后一句我直接用了。

 char *inet_ntoa(struct in_addr in);

当然,换个方式也行。

//inet_ntoa
 	
	char *c;
	c = inet_ntoa(inp);
	printf("the value is %s\n",c); 
	
	printf("\n↓********test of inet_ntoa**********\n")

inet_addr

原型:

 in_addr_t inet_addr(const char *cp);

inet_addr的作用跟inet_aton是一样的,结构也差不多,不过它是直接返回到一个in_addr_t类型里面,在上面讲bind的时候也提到过这是一种自定义的类型

//inet_addr
	in_addr_t inetaddr = 0;
	inetaddr = inet_addr(cp);
	printf("the value is %x\n",inetaddr);

注意:

The inet_addr() function converts the Internet host address cp from IPv4 numbers-and-dots nota‐
tion into binary data in network byte order. If the input is invalid, INADDR_NONE (usually -1)
is returned. Use of this function is problematic because -1 is a valid address
(255.255.255.255). Avoid its use in favor of inet_aton(), inet_pton(3), or getaddrinfo(3),
which provide a cleaner way to indicate error return.

这里讲到了inet_addr的一些弊端,最后还建议用inet_aton(), inet_pton(3), or getaddrinfo(3),这些来代替使用。

inet_pton

 #include <arpa/inet.h>

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

inet_pton的话相对于上面的略微复杂一点,毕竟传参也多一点,但是好处是什么呢?
好处在于它是兼容IPv4和IPv6的。

int af af 选AF_INET支持IPv4,选AF_INET6就支持IPv6
const char *src 传进去的是你要转换的地址,明显是一个输入型函数
void*dst 为什么是void*类型呢?因为它兼容两种模式,所以就得这样设置。
如果是ipv4的话,到时的dst就得跟struct in_addr挂钩,如果是ipv6的话,就要跟struct in6_addr挂钩了。这些黑体的结构体具体可以在
/usr/include/netinet/in.h里面找。

The address is converted to a struct in_addr and copied to dst, which
must be sizeof(struct in_addr) (4) bytes (32 bits) long.

这里是AF_INET的一小节description,大概就是说转换后的值就会丢到dst所指向的struct in_addr里面(这里是ipv4)

//这里开始测试inet_pton
	int ret_pton = 0;
	struct in_addr dst;
	ret_pton = inet_pton(AF_INET,IP_ADDR,&dst);
	if (ret_pton <= 0)
	{
		if(0 == ret_pton)
		{
			printf("the ipaddr is not a valid network address\n");
			exit (-1);
		}
		else
			perror("in_pton");
		return -1;
	}
	printf("dst = %x\n",dst.s_addr);

inet_ntop

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

作用就是跟inet_pton反着来。
1.const void* src换成了pton里面的dst了,注意不要搞混,
2.char *dst可以自己定义一个buffer,然后把转换后的值放里面。
3.socklen_t size,用来规定这个buffer的长度,注意是有规定最短长度的。

在这里插入图片描述
这个长度我还专门测试了一下

char test1[INET6_ADDRSTRLEN];
	char test2[INET_ADDRSTRLEN];

	printf("sizeof test1 is %ld\nsizeof test2 is %ld\n",sizeof(test1),sizeof(test2));

在这里插入图片描述
test1是ipv6的,test2是ipv4的,ipv6不熟悉,用ipv4来猜测检查一下
192.168.123.123
一共3*4+3 = 15 可能最后有个\0多算一个字节一共16个字节(因为sizeof单位是字节。)

下面是inet_ntop的测试代码

//这里开始测试inet_ntop
	char ntop_buffer[INET_ADDRSTRLEN];
	const char * ret_p = NULL;
	ret_p = inet_ntop(AF_INET,&dst,ntop_buffer,sizeof(ntop_buffer));
	if (NULL == ret_p)
	{
		perror("inet_ntop\n");
		exit(-1);
	}
		
	printf("the value is %s\n",ntop_buffer);

至此一些相关的辅助库函数都讲完了。
在学习的过程中我还发现了原来man手册inet_pton里面有一个示例代码的:

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>

 #include <string.h>

   int
   main(int argc, char *argv[])
   {
       unsigned char buf[sizeof(struct in6_addr)];
       int domain, s;
       char str[INET6_ADDRSTRLEN];

       if (argc != 3) {
           fprintf(stderr, "Usage: %s {i4|i6|<num>} string\n", argv[0]);
           exit(EXIT_FAILURE);
       }

       domain = (strcmp(argv[1], "i4") == 0) ? AF_INET :
                (strcmp(argv[1], "i6") == 0) ? AF_INET6 : atoi(argv[1]);

       s = inet_pton(domain, argv[2], buf);
       if (s <= 0) {
           if (s == 0)
               fprintf(stderr, "Not in presentation format");
           else
               perror("inet_pton");
           exit(EXIT_FAILURE);
       }

       if (inet_ntop(domain, buf, str, INET6_ADDRSTRLEN) == NULL) {
           perror("inet_ntop");
           exit(EXIT_FAILURE);
       }

       printf("%s\n", str);

       exit(EXIT_SUCCESS);
   }

真的写的非常的漂亮工整,很值得学习,那个socklent size的问题就是看完这个代码才从man手册里面仔细看。
printf和fprint的区别可以c_phoenix这位博主里面学习一下

发布了38 篇原创文章 · 获赞 1 · 访问量 1026

猜你喜欢

转载自blog.csdn.net/qq_40897531/article/details/104579000