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

时间:2022-11-09 17:20:07

前言:由于第五章主要介绍了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\n”, inet_ntoa(their_addr.sin_addr));
/* 这里将建立一个子进程来和刚刚建立的套接字进行通讯 */
if (!fork())
{
/* 这里是子进程 */
/* 这里就是我们说的错误检查! */
if (send(new_fd, “Hello, world!\n”, 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\n”);
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] = ‘\0’;
printf(“Received: %s”,buf);
close(sockfd);
return 0;
}