本章中关于socket代码详细功能解析在后续章节中都有提及,故遇到看不明白的可以先跳过。
<>1、服务端流程
* 第一步:调用socket函数创建套接字 #include <sys/socket.h> int socket(int domain,int type,
int protocol); //成功返回文件描述符,失败返回-1
* 第二步:调用bind函数分配ip地址和端口号 #include <sys/socket.h> int bind(int sockfd,struct
sockaddr*myaddr,socklen_t addrlen); //成功返回0,失败返回-1
* 第三步:调用listen函数转换为可接受请求状态 #include <sys/socket.h> int listen(int sockfd,int
backlog); //成功返回0,失败返回-1
* 第四步:调用accept函数受理连接请求 #include <sys/socket.h> int accept(int sockfd,struct
sockaddr*addr,socklen_t *addrlen); //成功返回文件描述符,失败返回-1
示例代码:
#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); }
<>2、客户端流程
* 第一步:调用socket函数创建套接字
* 第二步:调用connect函数向服务器端发送连接请求 #include <sys/socket.h> int connect(int sockfd,
struct sockaddr *serv_addr,socklen_t addrlen); //成功返回0,失败返回-1
socket函数创建套接字不马上分为服务器端和客户端。如果紧接着调用bind、listen,则变成服务器端套接字,如果调用connect则变成客户端套接字
示例代码:
#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); }
<>3、基于Linux的文件操作
*
在Linux中socket也被视为一种文件,故在网络数据传输过程中可以使用文件IO相关函数。但是在Windows中,socket需要调用特殊的数据传输相关函数,不同于文件。
*
文件描述符:是从3开始由大到小顺序编号,0、1、2分配给了标准IO
文件描述符对象
0标准输入
1标准输出
2标准错误
* 打开文件 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
//path为打开的目标文件名以及路径信息,flag是文件打开模式 int open(const char *path,int flag);
//成功时返回文件描述符,失败返回-1
文件打开模式
打开模式含义
O_CREAT必要时创建文件
O_TRUNC删除全部现有数据
O_APPEND维持现有数据,保存到后面
O_RDONLY只读打开
O_WRONLY只写打开
O_RDWR读写打开
示例代码:
#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include
<sys/socket.h> int main(void) { int fd1,fd2,fd3; fd1=socket(PF_INET,SOCK_STREAM,
0); fd2=open("test.dat",O_CREAT|O_WRONLY|O_TRUNC); fd3=socket(PF_INET,SOCK_DGRAM
,0); printf("file descriptor 1: %d",fd1); printf("file descriptor 2: %d",fd2);
printf("file descriptor 3: %d",fd3); close(fd1); close(fd2); close(fd3); return
0; }
* 关闭文件 #include <unistd.h> //fd为文件描述符 int close(int fd); //成功返回0,失败返回-1
* 数据写入文件 #include <unistd.h>
//fd显示数据传输对象的文件描述符,buf保存要传输数据的缓冲地址值,nbytes要传输数据的字节数 ssize_t write(int fd,const
void* buf,size_t nbytes); //成功返回写入字节数,失败返回-1
示例代码:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h>
void error_handling(char* message); int main(void) { int fd; char buf[] =
"Let's go!\n"; fd=open("data.txt",O_CREAT|O_WRONLY|O_TRUNC); if(fd==-1)
error_handling("open() error"); printf("file descriptor:%d \n",fd); if(write(fd,
buf,sizeof(buf))==-1) error_handling("write() error"); close(fd); return 0; }
void error_handling(char* message) { fputs(message,stderr); fputc('\n',stderr);
exit(1); }
* 读取文件中数据 #include <unistd.h> //fd文件描述符,buf要保存接收数据的缓冲地址值,nbytes要接收数据的最大字节数
ssize_tread(int fd,void* buf,size_t nbytes); //成功返回接收字节数(若遇到文件结尾返回0),失败返回-1
示例代码:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #
define BUF_SIZE 100 void error_handling(char* message); int main(void) { int fd;
char buf[BUF_SIZE]; fd=open("data.txt",O_RDONLY); if(fd==-1) error_handling(
"open() error"); if(read(fd,buf,sizeof(buf))==-1) error_handling("read() error")
; printf("file data: %s",buf); close(fd); return 0; } void error_handling(char*
message) { fputs(message,stderr); fputc('\n',stderr); exit(1); }
<>5、习题
*
套接字在网络编程中的作用是什么?为什么称它为套接字?
网络编程就是编写程序让两台联网的计算机相互交换数据。在我们不需要考虑物理连接的情况下,我们只需要考虑如何编写传输软件。操作系统提供了名为“套接字”,套接字是网络传输传输用的软件设备
socket英文原意是插座:我们把插头插到插座上就能从电网获得电力供给,同样,为了与远程计算机进行数据传输,需要连接到Internet,而变成中的“套接字”就是用来连接该网络的工具
*
在服务器端创建套接字后,会依次调用listen函数和accept函数。请比较并说明两者作用
listen:将套接字转为可接受连接状态
accept:受理连接请求,并且在没有连接请求的情况调用该函数,不会返回。直到有连接请求为止。二者存在逻辑上的先后关系
*
Linux中,对套接字数据进行I/O时可以直接使用I/O相关函数;而在Windows中则不可以。原因为何?
Linux把套接字也看作是文件,所以可以用文件I/O相关函数;而Windows要区分套接字和文件,所以设置了特殊的函数
*
创建套接字后一般会给它分配地址,为什么?为了完成地址分配需要调用哪些函数?
要在网络上区分来自不同机器的套接字,所以需要地址信息。分配地址是通过bind()函数实现
*
Linux中的文件描述符与Windows 的句柄实际上非常类似 请以套接字为对象说明它们的含义。
Linux的文件描述符是为了区分指定文件而赋予文件的整数值(相当于编号)。Windows的文件描述符其实也是套接字的整数值,其目的也是区分指定套接字。
*
底层文件I/O函数与ANSI标准定义的文件I/O函数之间有何区别?
ANSI标准定义的输入、输出函数是与操作系统(内核)无关的以C标准写成的函数。相反,底层文件I/O函数是操作系统直接提供的。理论上ANSI标准I/O提供了某些机制,性能上低于底层I/O
*
参考本书给出的示例low-open.c和low-read-c ,分别利用底层文件1/0 ANSI标准编写文件复制程序,可任意指定复制程序的使用方法
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #
define BUF_SIZE 100 void error_handling(char* message); int main(int argc, char*
argv[]) { if (argc != 3) { error_handling("传入参数错误"); } int fd1; int fd2; char
buf[BUF_SIZE]; //只读方式打开文件 fd1 = open(argv[1], O_RDONLY); if (fd1 == -1)
error_handling("fd1 open() error"); fd2 = open(argv[2], O_WRONLY | O_CREAT |
O_TRUNC); if (fd2 == -1) error_handling("fd2 open() error"); while (1) { int len
= read(fd1, buf, sizeof(buf)); if (len == -1) error_handling("read() error"); if
(len > 0 && len <= sizeof(buf)) { if (write(fd2, buf, len) == -1) error_handling
("write() error"); } else{ break; } } close(fd1); close(fd2); return 0; } void
error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr);
exit(1); }