第一章 理解网络编程和套接字
1.1理解网络编程和套接字
1.1.1网络编程和套接字概要
网络编程就是编写程序使得两台联网的计算机可以相互交换数据。
1.1.2构建电话套接字
套接字大致分为两种,一种为TCP,一种为UDP,下面以TCP为例。
- 调用socket函数(安装电话机时候)进行的对话
- 问:接电话需要什么?
- 答:当然是电话机了
下面创建的就是相当于电话机的套接字:
#include <sys/socket.h>
int socket(int domain, int type; int protocol);
- 调用bind函数(分配电话号码)时候进行的对话
- 问:请问您的电话号码是多少?
- 答:我的电话号码是1231233.
利用以下函数给创建好的套接字分配地址信息(IP地址和端口号)
#include <sys/socket,h>
int bind(int sockfd, struct sockaddr *myaddr, socklem_t addrlen);
- 调用listen函数(连接电话线)时候进行的对话
- 问:已架设完成电话机之后是否只需链接电话线
- 答:是的,现在只需要连接电话线就可以接听电话
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- 调用accept函数(拿起话筒)时候进行的对话
- 问:电话响了,我该怎么做
- 答:接起来就好
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
1.1.3编写“Hello World!"服务器端
//hello_server.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
void error_handling(char *message);
int main(int argc, char *argv[])
{
int serv_sock; //定义服务器套接字
int clnt_sock; //定义客户端套接字
struct sockaddr_in serv_addr; //构建服务器结构体
struct sockaddr_in clnt_addr; //构建客户端结构体
socklen_t clnt_addr_size;
//服务器要发给客户端的内容
char message[]="Hello World!";
if(argc != 2)
{
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
//构建服务器端套接字
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if(serv_sock == -1)
error_handling("socket() error");
//清理下结构体
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(argv[1]));
//构建“电话机”
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error");
//电话接到线上进行监听,也就是接通前的忙音
if(listen(serv_sock, 5) == -1)
error_handling("listen() error");
clnt_addr_size = sizeof(clnt_addr);
//接通了,第一个参数是服务器端的结构体,后面两个参数都是客户端的
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if(clnt_sock == -1)
error_handling("accept() error");
//往客户端套接字文件中写东西
write(clnt_sock, message, sizeof(message));
close(clnt_sock);
close(serv_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
1.1.4构建打电话套接字
//hello_client.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len;
if(argc != 3)
{
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock = socket(PF_INET, SOCK_STREAM, 0);
if(sock == -1)
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
serv_addr.sin_port = htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
error_handling("connect() error");
str_len = read(sock, message, sizeof(message)-1);
if(str_len == -1)
error_handling("read() error");
printf("Message from server : %s \n", message);
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
1.2基于Linux的文件操作
对于Linux而言,socket操作与文件操作没有任何区别,socket也被认为是文件的一种,因此在网络数据传输过程中自然可以使用文件I/O的相关函数。
1.2.1底层访问(Low-Level File Access)和文件描述符(File Descriptor)
实际上“底层”可以理解为“与标准无关的操作系统独立提供的”。
分配给标准输入输出及标准错误的文件描述符
文件描述符 | 对象 |
---|---|
0 | 标准输入:Standard Input |
1 | 标准输出:Standard Output |
2 | 标准错误:Standard Error |
1.2.2打开文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *path, int flag);
//成功返回文件描述符,失败时候返回-1
//path:文件名的字符串地址
//flag:文件打开模式信息
文件打开模式(flag参数)
打开模式 | 含义 |
---|---|
O_CREAT | 必要时创建文件 |
O_TRUNC | 删除全部现有数据 |
O_APPEND | 维持现有数据,保存到其后 |
O_RDONLY | 只读打开 |
O_WRONLY | 只写打开 |
O_RDWR | 读写打开 |
1.2.3关闭文件
#include <unistd.h>
int close(int fd);
//成功时候返回0,失败返回-1
//fd:需要关闭的文件或者套接字的文件描述符
1.2.4将数据写入文件
#include <unistd.h>
ssize_t write(int fd, const void * buf, size_t nbytes);
//成功时候返回写入的字节数,失败时候返回-1
//fd:显示数据传输对象的文件描述符
//buf:保存要传输数据的缓冲地址值
//nbytes:要传输数据的字节数
//此函数定义中,size_t是通过typedef声明的unsigned int类型。
//对于size_t来说,size_t前面多加一个s代表signed,即ssize_t是通过typedef声明的signed int 类型
1.2.5读取文件中的数据
#include <unistd.h>
ssize_t read(int fd, void * buf, size_t nbytes);
//成功时候返回接收的字节数量(但是遇到文件结尾则返回0),失败时候返回-1
//fd:显示数据接受对象的文件描述符
//buf:要保存接收数据的缓冲地址值
//nbytes:要接受数据的最大字节数
1.3课后题
1.3.1套接字在网络编程中的作用是什么?为什么叫他为套接字?
网络编程就是编写程序让两台联网的计算机相互交换数据。在我们不需要考虑物理连接的情况下,我们只需要考虑如何编写传输软件。操作系统提供了名为“套接字”,套接字是网络传输传输用的软件设备
1.3.2在服务器端创建套接字后,会依次调用listen函数和accept函数。请比较并说明两者作用
listen:将套接字转为可接受连接方式
accept:受理连接请求,并且在没有连接请求的情况调用该函数,不会返回。直到有连接请求为止。二者存在逻辑上的先后关系
1.3.3Linux中,对套接字数据进行I/O时可以直接使用I/O相关函数;而在Windows中则不可以。原因为何?
Linux把套接字也看作是文件,所以可以用文件I/O相关函数;而Windows要区分套接字和文件,所以设置了特殊的函数
1.3.4创建套接字后一般会给它分配地址,为什么?为了完成地址分配需要调用哪些函数?
要在网络上区分来自不同机器的套接字,所以需要地址信息。分配地址是通过bind()函数实现
1.3.5Linux中的文件描述符与Windows的句柄实际上非常类似。请以套接字为对象说明他们的含义。
Linux的文件描述符是为了区分指定文件而赋予文件的整数值(相当于编号)。Windows的文件描述符其实也是套接字的整数值,其目的也是区分指定套接字。
1.3.6底层文件I/O函数与ANSI标准定义的文件I/O函数之间有何区别?
ANSI标准定义的输入、输出函数是与操作系统(内核)无关的以C标准写成的函数。相反,底层文件I/O函数是直接提供的。理论上ANSI标准I/O提供了某些机制,性能上要优于底层I/O