• 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;
    }
    

      

  • 相关阅读:
    激活OFFICE2010时,提示choice.exe不是有效的win32程序
    Redis 学习之持久化机制、发布订阅、虚拟内存
    Redis 学习之事务处理
    Redis 学习之主从复制
    Redis 学习之常用命令及安全机制
    Redis 学习之数据类型
    Redis 学习之简介及安装
    Tomcat 虚拟主机配置
    mysql学习之权限管理
    mysql学习之主从复制
  • 原文地址:https://www.cnblogs.com/xqn2017/p/8946850.html
Copyright © 2020-2023  润新知