Linux网络编程学习(十) ----- Socket(第六章)

前言:由于第五章主要介绍了TCP和UDP协议以及两者的包头的字段以及相应的功能,这里就不介绍了,对着字段看功能就好了,后续开始学习第六章

1、Socket

Socket实质上就是提供了通信的端点,每个socket都用一个半相关描述{协议,本地地址,本地端口},完整的socket描述{协议,本地地址,本地端口,远程地址,远程端口}

可以这样解释:套接字是通过标准的UNIX文件描述符和其他的程序通讯的一个方法。

2、Socket的三种类型

1)流式套接字(SOCK_STREAM)

提供可靠、面向连接的通讯流,流式套接字使用了TCP协议,保证了数据传输的正确性。

2)数据报套接字(SOCK_DGRAM)

定义了一种无连接的服务,数据通过相互独立的报文进行传输,无序且不保证可靠、无差错。使用的是UDP协议,为什么无连接?由于UPD不像TCP那样维护一个打开的连接,只需要把数据打包,把远程的IP贴上,然后把这个包发出去即可,不需要建立连接。

既然数据会丢失,那如何保证程序正常工作呢?其实每个使用UDP的程序都要有自己的对数据进行确认的协议,比如TFTP协议定义了每一个发出去的数据包,远程在接收到之后都要反馈一个数据告诉本地程序已经收到,如果在5秒内未得到回应,就重发ACK信号。

3)原始套接字

用于协议开发,可进行底层操作,一般不涉及

3、套接字的基本结构

sockaddr用来存储套接字地址

struct sockaddr 
{
	unsigned short sa_family; /* address族, AF_xxx */
	char sa_data[14]; /* 14 bytes的协议地址 */
};

为了处理sockaddr,建立另一个结构sockaddr_in

/*因特网地址*/
struct in_addr 
{
	unsigned long s_addr;
};
struct sockaddr_in 
{
	short int sin_family; /* Internet地址族 */
	unsigned short int sin_port; /* 端口号 */
	struct in_addr sin_addr; /* Internet地址 */
	unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小)*/
};

 有一点很重要,就是一个指向struct sockaddr_in的指针可以声明指向一个sturct sockaddr 的结构。所以虽然socket() 函数需要一个structaddr * ,你也可以给他一个sockaddr_in * 。注意在structsockaddr_in 中,sin_family 相当于在 struct sockaddr 中的sa_family,需要设成“AF_INET”。最后一定要保证sin_port 和sin_addr 必须是网络字节顺序。这是因为sin_addr和sin_port时从IP和UDP协议层取出来的数据,而在IP和UDP协议层,是直接和网络相关的,所以必须用网络字节序 ,而sin_famliy只是内核用来判断struct sockaddr_in是存储什么类型数据,并且sin_famliy也不会发送到网络上,可以用主机字节序保存。

4、常用的系统调用函数,如socket(),bind(),listen(),accept(),send(),recv()各自的参数可以具体参考手册,用man (send/socket)查看,下面给个流式套接字例子介绍一下,服务器端代码,功能就是给远程客户端发送字符串"Hello,World"

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
/* 服务器要监听的本地端口 */
#define MYPORT 4000
/* 能够同时接受多少没有accept 的连接 */
#define BACKLOG 10
main()
{
	/* 在sock_fd 上进行监听,new_fd 接受新的连接 */
	int sock_fd, new_fd ;
	/* 自己的地址信息 */
	struct sockaddr_in my_addr;
	/* 连接者的地址信息*/
	struct sockaddr_in their_addr;
	int sin_size;
	/* 这里就是我们一直强调的错误检查.如果调用socket() 出错,则返回 */ 
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		/* 输出错误提示并退出 */
		perror(“socket”);
		exit(1);
	}
	/* 主机字节顺序 */
	my_addr.sin_family = AF_INET;
	/* 网络字节顺序,短整型 */
	my_addr.sin_port = htons(MYPORT);
	/* 将运行程序机器的IP 填充入s_addr */
	my_addr.sin_addr.s_addr = INADDR_ANY;
	/* 将此结构的其余空间清零 */
	bzero(&(my_addr.sin_zero), 8);
	/* 这里是我们一直强调的错误检查!! */ 
	if (bind(sockfd, (struct sockaddr *)&my_addr,sizeof(struct sockaddr)) == -1)
	{
		/* 如果调用bind()失败,则给出错误提示,退出 */
		perror(“bind”);
		exit(1);
	}
	/* 这里是我们一直强调的错误检查!! */
	if (listen(sockfd, BACKLOG) == -1)
	{
		/* 如果调用listen 失败,则给出错误提示,退出 */
		perror(“listen”);
		exit(1);
	}
	while(1)
	{
		/* 这里是主accept()循环 */
		sin_size = sizeof(struct sockaddr_in);
		/* 这里是我们一直强调的错误检查!! */
		if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1)
		{
			/* 如果调用accept()出现错误,则给出错误提示,进入下一个循环 */
			perror(“accept”);
			continue;
		}
		/* 服务器给出出现连接的信息 */
		printf(“server: got connection from %s
”, inet_ntoa(their_addr.sin_addr));
		/* 这里将建立一个子进程来和刚刚建立的套接字进行通讯 */
		if (!fork())
		{
			/* 这里是子进程 */
			/* 这里就是我们说的错误检查! */
			if (send(new_fd, “Hello, world!
”, 14, 0) == -1)
			{
				/* 如果错误,则给出错误提示,然后关闭这个新连接,退出 */
				perror(“send”);
				close(new_fd);
				exit(0);
			}
			/* 关闭new_fd 代表的这个套接字连接 */
			close(new_fd);
		}
	}
	/* 等待所有的子进程都退出 */
	while(waitpid(-1,NULL,WNOHANG) > 0);
}

  对应的客户端的程序代码:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
/* 服务器程序监听的端口号 */
#define PORT 4000
/* 我们一次所能够接收的最大字节数 */
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{
	/* 套接字描述符 */
	int sockfd, numbytes;
	char buf[MAXDATASIZE];
	struct hostent *he;
	/* 连接者的主机信息 */
	struct sockaddr_in their_addr;
	/* 检查参数信息 */
	if (argc != 2)
	{
		/* 如果没有参数,则给出使用方法后退出 */
		fprintf(stderr,“usage: client hostname
”);
		exit(1);
	}
	/* 取得主机信息 */
	if ((he=gethostbyname(argv[1])) == NULL)
	{
		/* 如果gethostbyname()发生错误,则显示错误信息并退出 */
		herror(“gethostbyname”);
		exit(1);
	}
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
	{
		/* 如果socket()调用出现错误则显示错误信息并退出 */
		perror(“socket”);
		exit(1);
	}
	/* 主机字节顺序 */
	their_addr.sin_family = AF_INET;
	/* 网络字节顺序,短整型 */
	their_addr.sin_port = htons(PORT);
	their_addr.sin_addr = *((struct in_addr *)he->h_addr);
	/* 将结构剩下的部分清零*/
	bzero(&(their_addr.sin_zero), 8);
	if(connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
	{
		/* 如果connect()建立连接错误,则显示出错误信息,退出 */
		perror(“connect”);
		exit(1);
	}
	if((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1)
	{
		/* 如果接收数据错误,则显示错误信息并退出 */
		perror(“recv”);
		exit(1);
	}
	buf[numbytes] = ‘’;
	printf(“Received: %s”,buf);
	close(sockfd);
	return 0;
}

  

原文地址:https://www.cnblogs.com/xqn2017/p/8946850.html