《TCP/IP网络编程》读书笔记
(1) Windows 下的 socket 程序依赖 Winsock.dll 或 ws2_32.dll,必须提前加载。DLL 有两种加载方式,请查看:动态链接库DLL的加载
(2) Linux 使用“文件描述符”的概念,而 Windows 使用“文件句柄”的概念;Linux 不区分 socket 文件和普通文件,而 Windows 区分;
(3) Linux 下 socket() 函数的返回值为 int 类型,而 Windows 下为 SOCKET 类型,也就是句柄。
(4) Linux 下使用 read() / write() 函数读写,而 Windows 下使用 recv() / send() 函数发送和接收。
(5) 关闭 socket 时,Linux 使用 close() 函数,而 Windows 使用 closesocket() 函数。
文件描述符 对象
0 标准输出
1 标准输出
2 标准错误
最新的DLL是 ws2_32.dll,大小为 69KB,对应的头文件为 winsock2.h。
使用#pragma命令,在编译时加载:
#pragma comment (lib, "ws2_32.lib")
WinSock 编程的第一步就是加载 ws2_32.dll,然后调用 WSAStartup() 函数进行初始化,并指明要使用的版本号
WSADATA wsaData;
WSAStartup( MAKEWORD(, ), &wsaData);
4. socket编程
(2) 使用bind()函数将套接字与特定的IP地址和端口绑定起来,只有这样,流经该IP地址和端口的数据才能交给套接字处理
(3) 使用connect()函数来建立连接
可以认为,sockaddr 是一种通用的结构体,可以用来保存多种类型的IP地址和端口号,而 sockaddr_in 是专门用来保存 IPv4 地址的结构体
另外还有 sockaddr_in6,用来保存 IPv6 地址
(7) accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号
(8) listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到accept()
(9) accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。
(10) TCP服务器端函数调用顺序:socket()->bind()->listen()->accept()->read()/write()->close()
(11) TCP客户端函数调用顺序:socket()->connect()->read()/write()->close()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h> #define BUF_SIZE 1024
void error_handling(char *message); int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
char message[BUF_SIZE];
int str_len, i; struct sockaddr_in serv_adr;
struct sockaddr_in clnt_adr;
socklen_t clnt_adr_sz; if(argc!=) {
printf("Usage : %s <port>\n", argv[]);
exit();
} serv_sock=socket(PF_INET, SOCK_STREAM, );
if(serv_sock==-)
error_handling("socket() error"); memset(&serv_adr, , sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[])); if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-)
error_handling("bind() error"); if(listen(serv_sock, )==-)
error_handling("listen() error"); clnt_adr_sz=sizeof(clnt_adr); for(i=; i<; i++)
{
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
if(clnt_sock==-)
error_handling("accept() error");
else
printf("Connected client %d \n", i+); while((str_len=read(clnt_sock, message, BUF_SIZE))!=)
write(clnt_sock, message, str_len); close(clnt_sock);
} close(serv_sock);
return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}
(2)基于linux环境的tcp回声客户端的实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h> #define BUF_SIZE 1024
void error_handling(char *message); int main(int argc, char *argv[])
{
int sock;
char message[BUF_SIZE];
int str_len;
struct sockaddr_in serv_adr; if(argc!=) {
printf("Usage : %s <IP> <port>\n", argv[]);
exit();
} sock=socket(PF_INET, SOCK_STREAM, );
if(sock==-)
error_handling("socket() error"); memset(&serv_adr, , sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[]);
serv_adr.sin_port=htons(atoi(argv[])); if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-)
error_handling("connect() error!");
else
puts("Connected..........."); while()
{
fputs("Input message(Q to quit): ", stdout);
fgets(message, BUF_SIZE, stdin); if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
break; write(sock, message, strlen(message));
str_len=read(sock, message, BUF_SIZE-);
message[str_len]=;
printf("Message from server: %s", message);
} close(sock);
return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}
(2) 使用 shutdown() 函数可以只断开一条数据传输通道,而保留另一条
int shutdown(int sock, int howto); //Linux
int shutdown(SOCKET s, int howto); //Windows
sock 为需要断开的套接字,howto 为断开方式
howto 在 Linux 下有以下取值:
- SHUT_RD:断开输入流。套接字无法接收数据(即使输入缓冲区收到数据也被抹去),无法调用输入相关函数。
- SHUT_WR:断开输出流。套接字无法发送数据,但如果输出缓冲区中还有未传输的数据,则将传递到目标主机。
- SHUT_RDWR:同时断开 I/O 流。相当于分两次调用 shutdown(),其中一次以 SHUT_RD 为参数,另一次以 SHUT_WR 为参数。
howto 在 Windows 下有以下取值:
- SD_RECEIVE:关闭接收操作,也就是断开输入流。
- SD_SEND:关闭发送操作,也就是断开输出流。
- SD_BOTH:同时关闭接收和发送操作。
shutdown() 用来关闭连接,而不是套接字,不管调用多少次 shutdown(),套接字依然存在,直到调用 close() / closesocket() 将调用 close()/closesocket() 关闭套接字时,或调用 shutdown() 关闭输出流时,都会向对方发送 FIN 包。FIN 包表示数据传输完毕,计算机收到 FIN 包就知道不会再有数据传送过来了
(3) 默认情况下,close()/closesocket() 会立即向网络中发送FIN包,不管输出缓冲区中是否还有数据,而shutdown() 会等输出缓冲区中的数据传输完毕再发送FIN包, 也就意味着,调用 close()/closesocket() 将丢失输出缓冲区中的数据,而调用 shutdown() 不会。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h> #define BUF_SIZE 30
void error_handling(char *message); int main(int argc, char *argv[])
{
int serv_sd, clnt_sd;
FILE * fp;
char buf[BUF_SIZE];
int read_cnt; struct sockaddr_in serv_adr, clnt_adr;
socklen_t clnt_adr_sz; if(argc!=) {
printf("Usage: %s <port>\n", argv[]);
exit();
} fp=fopen("file_server.c", "rb");
serv_sd=socket(PF_INET, SOCK_STREAM, ); memset(&serv_adr, , sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[])); bind(serv_sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
listen(serv_sd, ); clnt_adr_sz=sizeof(clnt_adr);
clnt_sd=accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz); while()
{
read_cnt=fread((void*)buf, , BUF_SIZE, fp);
if(read_cnt<BUF_SIZE)
{
write(clnt_sd, buf, read_cnt);
break;
}
write(clnt_sd, buf, BUF_SIZE);
} shutdown(clnt_sd, SHUT_WR);
read(clnt_sd, buf, BUF_SIZE);
printf("Message from client: %s \n", buf); fclose(fp);
close(clnt_sd); close(serv_sd);
return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}
(2)file_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h> #define BUF_SIZE 30
void error_handling(char *message); int main(int argc, char *argv[])
{
int sd;
FILE *fp; char buf[BUF_SIZE];
int read_cnt;
struct sockaddr_in serv_adr;
if(argc!=) {
printf("Usage: %s <IP> <port>\n", argv[]);
exit();
} fp=fopen("receive.dat", "wb");
sd=socket(PF_INET, SOCK_STREAM, ); memset(&serv_adr, , sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[]);
serv_adr.sin_port=htons(atoi(argv[])); connect(sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr)); while((read_cnt=read(sd, buf, BUF_SIZE ))!=)
fwrite((void*)buf, , read_cnt, fp); puts("Received file data");
write(sd, "Thank you", );
fclose(fp);
close(sd);
return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}
不同 CPU 保存和解析数据的方式不同(主流的 Intel 系列 CPU 为小端序),小端序系统和大端序系统通信时会发生数据解析错误。因此在发送数据前,要将数据转换为统一的格式——网络字节序(Network Byte Order)。网络字节序统一为大端序。
ping 域名可以查看域名对应的IP地址
nslookup命令可以查看计算机中注册的默认DNS服务器地址
(1) 下列函数可以通过传递字符串格式的域名获取IP地址
#include <netdb.h>
struct hostent *gethostbyname(const char *hostname);
成功时返回hostnet结构体指针,失败时返回NULL指针
例子:gethostbyname.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
void error_handling(char *message); int main(int argc, char *argv[])
{
int i;
struct hostent *host;
if(argc!=) {
printf("Usage : %s <addr>\n", argv[]);
exit();
} host=gethostbyname(argv[]);
if(!host)
error_handling("gethost... error"); printf("Official name: %s \n", host->h_name); for(i=; host->h_aliases[i]; i++)
printf("Aliases %d: %s \n", i+, host->h_aliases[i]); printf("Address type: %s \n",
(host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6"); for(i=; host->h_addr_list[i]; i++)
printf("IP addr %d: %s \n", i+,
inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}
struct hostnet
{
char *h_name;
char **h_aliases;
int h_addrtype;
int h_lenght;
char **h_addr_list;
}
(2) gethostbyaddr()函数利用IP地址获取域相关信息
#include <netdb.h>
struct hostnet *gethostbyaddr(const char *addr, socklen_t len, int fanily);
成功时返回hostnet结构体变量地址值,失败时返回NULL指针
addr:含有IP地址信息的in_addr结构体指针
len: 向第一个参数传递的地址信息的字节数,IPv4时为4,IPv6时为6
family: 传递地址族信息,IPv4时为AF_INET, IPv6时为AF_INET6
例子:gethostbyaddr.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
void error_handling(char *message); int main(int argc, char *argv[])
{
int i;
struct hostent *host;
struct sockaddr_in addr;
if(argc!=) {
printf("Usage : %s <IP>\n", argv[]);
exit();
} memset(&addr, , sizeof(addr));
addr.sin_addr.s_addr=inet_addr(argv[]);
host=gethostbyaddr((char*)&addr.sin_addr, , AF_INET);
if(!host)
error_handling("gethost... error"); printf("Official name: %s \n", host->h_name); for(i=; host->h_aliases[i]; i++)
printf("Aliases %d: %s \n", i+, host->h_aliases[i]); printf("Address type: %s \n",
(host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6"); for(i=; host->h_addr_list[i]; i++)
printf("IP addr %d: %s \n", i+,
inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
(4) 将字符串信息转换为网络字节序的整数型
#include <arpa/inet.h>
in_addr_t inet_addr(const char *string);
成功时返回32位大端序整数型值,失败时返回INADDR_NONE
(5) inet_aton()也将字符串形式IP地址转换位32位网络字节序整数并返回,只不过该函数利用in_addr结构体,且其使用频率跟高
#include <arpa/inet.h>
int inet_aton(const char *string, struct in_addr *addr);
成功时返回1(true),失败时返回0(false)
(6) inet_ntoa()函数可以把网络字节序整数型IP地址转换成字符串形式
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr adr);
成功时返回转换的字符串地址值,失败时返回-1;
调用完该函数后应立即将字符串信息复制到其他内存空间,因为,若再次调用inet_ntoa函数,则有可能覆盖之前保存的字符串信息
struct sockaddr_in addr;
char *serv_ip = "211.217.168.13";
char *serv_port "";
memset(&addr, , sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(serv_ip);
addr.sin_port = htons(atoi(serv_port));
利用常数INADDR_ANY分配服务器的IP地址,可以自动获取运行服务器端的计算机IP地址
addr.sin_addr.s_addr = htonl(INADDR_ANY);
getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);
setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen)
- SO_SNDBUF:输入缓冲大小相关可选项
- SO_RCVBUF:输出缓冲大小相关可选项
- SO_REUSEADDR:该可选项设置为TRUE可将Time_wait状态下的套接字端口号重新分配给新的套接字
TCP_NODELAY设置为1可禁用Nagle算法
#include <unistd.h>
pid_t fork(void);
父进程:fork函数返回子进程的ID
子进程:fork函数返回0
销毁僵尸进程1:利用wait函数
#include <unistd.h>
pid_t wait(int *statloc);
->成功时返回终止的子进程ID,失败时返回-1
子进程终止时传递的返回值将保存到statloc所指内存空间,需要用下列宏进行分离
- WIFEXITED 子进程正常终止时返回“真”true
- WEXITSTATUS 返回子进程的返回值
销毁僵尸进程2:利用waitpid函数
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statloc, int options);
->成功时返回终止的子进程ID,失败时返回-1
pid 等待终止的目标子进程ID,若传递-1,则可以等待任意子进程终止
(4) options 传递sys/wait.h中声明的常量WNOHANG,即使没有终止的子进程也不会进入组赛状态,而是返回0并退出函数
(5)信号与signal函数
#include <signal.h>
void (*signal(int signal, void (*func)(void)))(int);
->为了在产生信号时调用,返回之前注册的函数指针
(6) 利用sigaction函数进行信号处理
#include <signal.h>
int sugacyion(int signo, const struct sigaction *act, struct sigaction *oldact);
->成功时返回0,失败时返回-1
通过fork函数复制套接字文件描述符后,同一端口将对应多个套接字,只有这些套接字描述符都终止,才能销毁套接字
13.进程间通信
创建管道的函数:
#include <unistd.h>
int pipe(int filedes[]);
->成功时返回0,失败时返回-1
filedes[0]:通过管道接收数据时使用的文件描述符,即管道出口
filedse[1]:通过管道传输数据时使用的文件描述符,即管道入口
14.I/O复用
(1) 针对fd_set变量的操着的宏:
FD_ZERO(fd_set *fdset)
FD_SET(int fd, fd_set *fdset)
FD_CLR(int fd, fd_set *fdset)
FD_ISSET(fint fd, d_set *fdset)
(2) select函数:
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
15.多种I/O函数
(1) 收到MSG_OOB紧急消息时,操着系统将产生SIGURG消息,并调用注册的信号处理函数
(2) 处理SIGURG信号时必须指定处理信号的进程,而geipid函数返回调用此函数的进程ID
fcntl(recv_sock, F_SETOWN, getpid());
上述调用的含义是“将文件描述符recv_sock指向的套接字拥有者(F_SETOWN)改为把getpid函数返回值用作ID的进程
(4) 紧急指针指向紧急消息的下一个位置(偏移量+1),紧急消息的意义在于督促消息处理,而非紧急传输形式受限的消息
(5) 调用recv函数的同时传递MSG_PEEK可选项,是为了保证即使不存在待读取的数据也不会进入阻塞状态,设置MSG_PEEK选项并调用recv函数时,即使读取了输入缓冲的数据也不会删除,该选项通常与MSG_DONTWAIT合作,用于调用以非阻塞方式验证待读取数据存在与否的函数
《TCP/IP网络编程》读书笔记的更多相关文章
- csapp读书笔记-并发编程
这是基础,理解不能有偏差 如果线程/进程的逻辑控制流在时间上重叠,那么就是并发的.我们可以将并发看成是一种os内核用来运行多个应用程序的实例,但是并发不仅在内核,在应用程序中的角色也很重要. 在应用级 ...
- CSAPP 读书笔记 - 2.31练习题
根据等式(2-14) 假如w = 4 数值范围在-8 ~ 7之间 2^w = 16 x = 5, y = 4的情况下面 x + y = 9 >=2 ^(w-1) 属于第一种情况 sum = x ...
- CSAPP读书笔记--第八章 异常控制流
第八章 异常控制流 2017-11-14 概述 控制转移序列叫做控制流.目前为止,我们学过两种改变控制流的方式: 1)跳转和分支: 2)调用和返回. 但是上面的方法只能控制程序本身,发生以下系统状态的 ...
- CSAPP 并发编程读书笔记
CSAPP 并发编程笔记 并发和并行 并发:Concurrency,只要时间上重叠就算并发,可以是单处理器交替处理 并行:Parallel,属于并发的一种特殊情况(真子集),多核/多 CPU 同时处理 ...
- 读书笔记汇总 - SQL必知必会(第4版)
本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...
- 读书笔记--SQL必知必会18--视图
读书笔记--SQL必知必会18--视图 18.1 视图 视图是虚拟的表,只包含使用时动态检索数据的查询. 也就是说作为视图,它不包含任何列和数据,包含的是一个查询. 18.1.1 为什么使用视图 重用 ...
- 《C#本质论》读书笔记(18)多线程处理
.NET Framework 4.0 看(本质论第3版) .NET Framework 4.5 看(本质论第4版) .NET 4.0为多线程引入了两组新API:TPL(Task Parallel Li ...
- C#温故知新:《C#图解教程》读书笔记系列
一.此书到底何方神圣? 本书是广受赞誉C#图解教程的最新版本.作者在本书中创造了一种全新的可视化叙述方式,以图文并茂的形式.朴实简洁的文字,并辅之以大量表格和代码示例,全面.直观地阐述了C#语言的各种 ...
- C#刨根究底:《你必须知道的.NET》读书笔记系列
一.此书到底何方神圣? <你必须知道的.NET>来自于微软MVP—王涛(网名:AnyTao,博客园大牛之一,其博客地址为:http://anytao.cnblogs.com/)的最新技术心 ...
- Web高级征程:《大型网站技术架构》读书笔记系列
一.此书到底何方神圣? <大型网站技术架构:核心原理与案例分析>通过梳理大型网站技术发展历程,剖析大型网站技术架构模式,深入讲述大型互联网架构设计的核心原理,并通过一组典型网站技术架构设计 ...
随机推荐
- 大二组队(NABCD)
(Need)需求 很多时候,外人都不了解我们的校园.为了宣传铁大.让更多人了解校园.走进铁大. (Approach)做法 我们有最基本的展示.对校园风貌有基本的讲解.有论坛供应大家讨论. (Benef ...
- SpringMVC 配置.html拦截时,返回JSON数据时出现406错误解决方案
[说明]在SpringMVC框架的使用中常常会使用@ResponseBody注解,修饰"处理器"(Controller的方法),这样在处理器在返回完毕后,就不走逻辑视图,而是将返回 ...
- C#中字节数组byte[]和字符串string类型的相互转换
C#中字节数组byte[]和字符串string类型的相互转换: string转byte[]: byte[] byteArray = System.Text.Encoding.Default.GetBy ...
- java 发送简单邮件(不带附件)
引入依赖 邮件实体类 可用邮件服务器地址(网易为例) 邮件工具类 import com.me.beans.Mail; import lombok.extern.slf4j.Slf4j; import ...
- codeforces 1284C. New Year and Permutation(组合数学)
链接:https://codeforces.com/problemset/problem/1284/C 题意:定义一个framed segment,在区间[l,r]中,max值-min值 = r - ...
- MySql -- default 默认约束
常用数据库约束: 一.default 默认约束: 二.not null:非空约束,指定某列不为NULL: 三.unique:唯一约束,指定某列和几列组合的数据不能重复: 四.primary key:主 ...
- MySql5.6表操作
MySql5.6表操作 数据类型 整型 浮点型 字符类型 日期类型 枚举类型与集合类型 约束条件 Primary key Unique key Not null Foreign key 创建表的完整语 ...
- sql sever登录问题
重启电脑后会发现连不上数据库了 按下win+r:输入cmd.连接你的ip,(telnet 127.0.0.1 xxxx)发现连接不上 正在连接127.0.0.1..无法打开到主机的连接. 在端口 14 ...
- 515,前端性能优化--减少http请求(待补充)
对于影响页面呈选的因素有三个地方:服务器连接数据库并计算返回数据,http请求以及数据(文件)经过网络传输,文件在浏览器中计算渲染呈选:其中大约80%的时间都消耗在了http的请求上,所以要想大幅度的 ...
- HTML5学习(4)文本元素
使用VSCode编辑器,内置emmet插件. ctrl+/ 注释/取消注释 ctrl+enter 新起一行 ctrl+shift+enter 往上新起一行 h$*4>lorem10 <h1 ...