Linux网络编程【一】:TCP socket套接字详解

1.socket

socket这个词可以表示很多概念: 在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP地址+端口号”就称为

socket。

在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么这两个socket组成 的socketpair就唯一标识一个连接。 socket本身

有“插座”的意思,因此用来描述网络连接的一 对一关系。

2.网络字节序

      发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,

是按内存地址从低到高的顺序保存,因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。

        TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

Linux网络编程【一】:TCP socket套接字详解_第1张图片

3. socket地址的数据类型及相关函数

Linux网络编程【一】:TCP socket套接字详解_第2张图片

IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,IPv6地sockaddr_in6

结构体表示,包括16位端口号、 128位IP地址和一些 控制字段。 UNIX Domain Socket的地址格式定义在sys/un.h中,用sockaddr_un

结构体表示。各 种socket地址结构体的开头都是相同的,前16位表示整个结构体的长度(并不是所有UNIX的实现 都有长度字段,如

Linux就没有),后16位表示地址类型。 IPv4、 IPv6和UNIXDomain Socket的地 址类型分别定义为常数AF_INET、 AF_INET6、 

AF_UNIX。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确

定结构体中的内容。

Linux网络编程【一】:TCP socket套接字详解_第3张图片

基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位的IP 地址。但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示和in_addr表示之间转换。

字符串转in_addr:

Linux网络编程【一】:TCP socket套接字详解_第4张图片

in_addr转字符串:



基于TCP的socket编程主要分为服务器和客户端。

服务器端的流程:

1.创建套接字:


domain:协议域,又称协议族(family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,UnixSocket)、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

type:指定Socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是一种面向连接的Socket,针对于面向连接的TCP服务应用。

protocol:指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。一般设为0,会自动选择第二个参数类型对应的默认协议。

返回值信息:如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET(Linux下失败返回-1)。

套接字描述符:是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据

结构的对应关系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述

符就可以找到其对应的套接字数据结构。每个进程在自己的进程空间里都有一个套接字描述符表但是套接字数据结构都是在操作系

统的内核缓冲里。

2.绑定:将一本地地址与一套接口捆绑。本函数适用于未连接的数据报或流类套接口,在connect()或listen()调用前使用。当用socket()创建套接口后,它便存在于一个名字空间(地址族)中,但并未赋名。bind()函数通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆绑(主机地址/端口号)。


socket:是一个套接字描述符。
address:是一个sockaddr结构指针,该结构中包含了要结合的地址和端口号。
address_len:确定address缓冲区的长度。
返回值:如果函数执行成功,返回值为0,否则为SOCKET_ERROR,返回-1。

3.监听:创建一个套接口,并监听申请的连接


sockfd:用于标识一个已捆绑未连接套接口的描述字。
backlog:等待连接队列的最大长度。一般5-10 不可过大

4.获取连接:在套接口接受一个连接


sockfd :相当简单,是和 listen() 中一样的套接字描述符。

addr: 是个指向局部的数据结构 sockaddr_in 的指针。这是要求接入的信息所要去的地方(你可以测定那个地址在那个端口呼叫你)。

addrlen:在它的地址传递给 accept 之前,addrlen 是个局部的整形变量,设置为 sizeof(struct sockaddr_in)。accept 将不会将多余的字节给 addr。如果你放入的少些,那么它会通过改变 addrlen 的值反映出来。

addr,addrlen均为输入输出型参数

5.关闭套接字


客户端流程:

(1)创建套接字(socket)
(2)向服务器发出连接请求(connect)
(3)和服务器端进行通信(read/write)

(4)关闭套接字

其中需要调用connect进行连接服务器

Linux网络编程【一】:TCP socket套接字详解_第5张图片

sockfd参数指定一个未连接的数据报或流类套接口。如套接口未被捆绑,则系统赋给本地关联一个唯一的值,且设置套接口为已捆绑。请注意若名字结构中的地址域为全零的话,则connect()将返回WSAEADDRNOTAVAIL错误。
对于流类套接口(SOCK_STREAM类型),利用名字来与一个远程主机建立连接,一旦套接口调用成功返回,它就能收发数据了。

具体实现代码:

服务器端server:

#include
#include
#include
#include
#include
#include
#include
#define _BACKLOG_ 10

int start(const char* _ip, int _port)
{

	int sock = socket(AF_INET, SOCK_STREAM, 0);//1.创建套接字 返回值相当文件描述符
	if(sock < 0)
	{
		perror("socket");
		exit(2);
	}

	//绑定
	struct sockaddr_in server_socket;
	server_socket.sin_family = AF_INET;
	server_socket.sin_port = htons(_port);
	server_socket.sin_addr.s_addr = inet_addr(_ip);

	if(bind(sock, (struct sockaddr*)&server_socket, sizeof(server_socket)) < 0)
	{
		perror("bind error!");
		exit(3);
	}

	if(listen(sock, _BACKLOG_) < 0)
	{
		perror("listen");
		exit(4);
	}
	return sock;
}

int main(int argc, char* argv[])
{
	if(argc != 3)
	{
		printf("argv");
		return 1;
	}
	int listen_sock = start(argv[1],atoi(argv[2]));
	struct sockaddr_in remote;
	int len = sizeof(remote);
	char buf[1024];
	while(1)
	{
		int sock = accept(listen_sock, (struct sockaddr*)&remote,&len);
		if(sock < 0)
		{
			perror("accept error");
			continue;
		}

		printf("connect...  ip is: %s  port is: %d \n", inet_ntoa(remote.sin_addr), ntohs(remote.sin_port));
		while(1)
		{
			ssize_t s = read(sock, buf, sizeof(buf)-1);
			if(s > 0)
			{
		 		buf[s] = '\0';
				printf("client :# %s\n", buf);
				write(sock, buf,strlen(buf));
			}
			else
			{
		 		printf("client close....\n");
				break;
			}
		}
	}
	close(listen_sock);
	return 0;
}


客户端client:

#include
#include
#include
#include
#include
#include
#include

int start(int _port, const char* _ip)
{
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0)
	{
		perror("socket");
		exit(0);
	}
	return sock;
}

int main(int argc, char * argv[])
{
	//不需要监听、绑定、accept
	if(argc != 3)
	{
		return 1;
	}
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0)
	{
		perror("sock");
		exit(1);
	}

	struct sockaddr_in peer;
	peer.sin_family = AF_INET;
	peer.sin_port = htons(atoi(argv[2]));
	peer.sin_addr.s_addr = inet_addr(argv[1]);

	if(connect(sock, (struct sockaddr*)&peer, sizeof(peer)))
	{
		perror("connect...");
		exit(2);
	}
	
	char buf[1024];

	while(1)
	{
		printf("please user enter: ");
		fflush(stdout);
		ssize_t s = read(0, buf, sizeof(buf)-1);

		if(s > 0)
		{
			buf[s-1] = 0;
			write(sock, buf, strlen(buf));
			ssize_t s1 = read(sock, buf,sizeof(buf)-1);

			if(s1 > 0)
			{
				buf[s1] = 0;
				printf("server say $ :%s\n",buf);
			}
		}
	}
	close(sock);
	return 0;
}

你可能感兴趣的:(计算机网络,Linux)