第1章 理解网络编程和套接字
网络编程中接受连接请求的套接字创建过程:
第一步:调用socket函数创建套接字;
第一步:调用bind函数分配IP地址和端口号;
第三步:调用listen函数转为可接收请求状态;
第四步:调用accept函数受理连接请求。
//Linux下的C语言编译器-GCC(GNU Compiler Collection,GNU编译器集合) gcc hello_server.c -o hserver //编译hello_server.c文件并生成可执行文件hserver,命令中的-o是用来指定可执行文件名的可选参数
以_t为后缀的数据类型,元数据类型(primitive),在sys/types.h头文件中一般由typedef声明定义,为了适应系统、时代的变化,如过去16位操作系统时代,int类型是16位的,
文件描述符(File Descriptor):0、1、2分配给标准输入输出及标准错误
WSA(Windows Sockets Asynchronous,Windows异步套接字)
//调用WSAStartup函数,设置程序中用到的Winsock版本,并初始化相应版本的库 int WSAStartup(WORD wVersionRequested, LPWSADATA, lpWSAData); //wVersionRequested 程序员要用的Winsock版本信息 lpWSAData WSADATA结构体变量的地址值
第2章 套接字类型与协议设置
int socket(int domain, int type, int protocol) //协议族(PF,protocol family),传输方式,协议//Windows返回SOCKET句柄
SOCK_STREAM、SOCK_DGRAM(datagram):存在数据边界意味着接收数据的次数应和传输次数相同
//Linux I/O函数 read/write ssize_t write(int fd, const void* buf, size_t nbytes);//文件描述符 缓冲地址值 字节数 ssize_t read(int fd, void* buf, size_t nbytes);//成功时返回接收的字节数(但遇到文件末尾则返回0),失败使返回-1。 size_t是通过typedef声明的unsigned int类型,ssize_t前面多加的s代表signed //Windows严格 区分文件I/O函数和套接字I/O函数 send/recv int send(SOCKET s, const char* buf, int len, int flags);//p23 int recv(SOCKET s, const char* buf, int len, int flags);
第3章 地址族与数据序列
字节序(Order)与网络字节序
ox 12345678 --2种:大端序(big endian)0x12 0x34 0x56 0x78;小端序(little endian)反之
在通过网络传输数据时约定统一方式,这种约定称为网络字节序(network byte order),统一大端序
字节序转换htons(h、to、n、s)(把short型数据从主机字节序转化为网络字节序)
inet_aton函数和inet_addr函数都将字符串形式(点分...)IP地址转换为32位网络字节序整数并返回,inet_aton会自动把转换后的IP地址信息填入in_addr结构体变量
struct sockaddr_in { sa_family_t sin_family; //地址族(address family) unit16_t sin_port; //16位TCP/UDP端口号 struct in_addr sin_addr; //32位IP地址 char sin_zero[8];//不使用 }; struct in_addr { in_addr_t s_addr; //32位IPV4地址 };
in_addr_t inet_addr(const char* string) //in_addr_t IP地址 声明为unit32_t
INADDR_ANY 可自动获取运行服务器端的计算机IP地址,若只有一个NIC,则精确匹配,127.0.0.1是回送地址(loopback address),指的是计算机自身IP地址
第4、5章 基于TCP的服务器端/客户端
int listen(int sok, int backlog); //backlog连接请求队列(Queue)的长度,若为5,则队列长度为5,表示最多使5个连接请求进入队列
客户端的IP地址和端口在调用connect函数时自动分配,无需调用标记的bind函数进行分配。
ACK号 -> SEQ(sequence)号 + 传递的字节数 + 1
第6章 基于UDP的服务器端/客户端
不必调用TCP连接中调用的listen函数和accept函数,UDP中只有创建套接字的过程和数据交换过程。
调用sendto函数时自动分配IP和端口号;在UDP通信过程中使I/O函数调用次数保持一致。
针对UDP套接字调用connect函数并不意味着要与对方UDP套接字连接,这只是向UDP套接字注册目标IP和端口信息。不用sendto每次都填
第7章 优雅地断开套接字连接
int shutdown (int sock, int howto); p124
第8章 域名及网络地址
DNS(Domain Name System)
struct hostent //host entry { char * h_name; //official name char ** h_aliases; //alias list int h_addrtype; //host address type int h_length; //address length char ** h_addr_list; //address list }
第9章 套接字的多种可选项
服务器端先断开连接的时候,套接字处在Time-wait过程中,相应端口是正在使用的状态,所以再启动,bind函数调用过程报错,而客户端每次运行程序时都会动态分配端口号
SO_REUSEADDR的默认值为0(假),这就意味着无法将Time_wait状态下的套接字端口号重新分配给新的套接字。改为1用setsockopt函数
第10章 多进程服务器端
CPU核的个数与可同时运行的进程数相同。相反,若进程数超过核数,进程将分时使用CPU资源。
“应该向创建子进程的父进程传递子进程的exit参数值或return语句的返回值”,“父母要负责收回自己生的孩子”;为了销毁子进程,父进程应主动请求获取子进程的返回值。
pid_t wait(int* statloc);//子进程终止时传递的返回值保存到函数参数所指内存空间 //宏分离 WIFEXIED WEXITSTATUS //调用wait函数时,如果没有已终止的子进程,那么程序将阻塞(Blocking)直到有子进程终止 pid_t waitpid(pid_t pid, int * statloc, int options);//P165
信号与signal函数 P168
SIGALRM:已到通过alarm函数注册的时间 SIGINT:输入Ctrl+c SIGCHLD:子进程终止 sigaction(SIGALRM, &act, 0);//注册SIGALRM信号的处理器 //“注册信号”--signal()/sigaction
fork函数复制文件描述符,父进程将2个套接字(一个是服务器端套接字,另一个是与客户端连接的套接字)文件描述符复制给子进程。
I/O程序分割的意义 p180
第11章 进程间通信
进程具有完全独立的内存结构,就连通过fork函数创建的子进程也不会与父进程共享内存空间
数据进入管道后成为无主数据
int pipe(int filedes[2]);//filedes[0]接受 管道出口;filedes[1]传输 管道入口
第12章 I/O复用
能在不创建新进程的同时向多个客户端提供服务 “教师 学生 一对一 一对多举手”
//使用select函数时可以将多个文件描述符集中到一起统一监视,监视项称为“事件” int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, cosnt struct timeval* timeout); //调用select函数后,除发生变化的文件描述符对应位外,剩下的所有位初始化为0。结构体timeval的成员tv_sec和tv_usec的值将被替换为超时前剩余时间;因此调用select函数前,每次都需要初始化timeval结构体变量 p203 FD_ISSET(int fd, fd_set* fdset):若参数fdset指向的变量中包含文件描述符fd的信息,则返回“真”;fdset一般为select之后的副本,值仍为1的位置上的文件描述符发生了变化
第13章 多种I/O函数
除紧急指针的前面1个字节外,数据接收方将通过调用常用输入函数读取剩余部分。p218
设置MSG_PEEK选项并调用recv函数时,即使读取了输入缓冲的数据也不会删除。
readv & writev 函数
struct iovec { void* iov_base; size_t iov_len; }
"Windows中并不存在Linux那样的信号处理机制“ sigaction / select p225
第14章 多播与广播
struct ip_mreq//p233 { struct in_addr imr_multiaddr; struct in_addr imr_interface; }
多播即使在跨越不同网络的情况下,只要加入多播组就能接收数据。相反,广播只能向同一网络中的主机传输数据。
第15、16章 使用标准I/O函数 I/O流分离
使用标准I/O函数时会得到额外的缓冲支持
FILE* fdopen(int fildes, const char* mode);//p249 将创建套接字时返回的文件描述符转换为标准I/O函数中使用的FILE结构体指针 int fileno(FILE* stream);//p251 int dup(int fildes); int dup2(int fildes, int fildes2);//p261
第17章 优于select的epoll
int epoll_create(int size);//p268 int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event); //结构体epoll_event用于保存发生变化的(发生事件)的文件描述符 epoll_ctl(A, EPO_CTL_ADD, B, C);//epoll例程A中注册文件描述符B,主要目的是监视参数C中的事件 int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout); //成功时返回发生事件的文件描述符数,失败时返回-1。同时在第二个参数指向的缓冲中保存发生事件的文件描述符集合。因此,无需像select那样插入针对所有文件描述符的循环。
条件触发和边缘触发
条件触发方式中,只要输入缓冲中还有数据,就将以事件方式再次注册
EPOLLET:以边缘触发(Edge Trigger)的方式得到事件通知
int fcntl(int filedes, int cmd, ...);//p278 更改或读取文件属性
第18章 多线程服务器端的实现
-
线程的创建和上下文切换比进程的创建和上下文切换更快
-
线程间交换数据时无需特殊技术
线程隔开栈区域,共享数据区和堆
//p287 int pthread_create( pthread_t* restrict thread, const pthread_attr_t* restrict arr, void* (* start_routine)(void*), void* restrict arg ); thread--保存新创建线程ID的变量地址值。线程与进程相同,也需要用于区分不同线程的ID。 attr--用于传递线程属性的参数,传递NULL时,创建默认属性的线程。 start_routine--相当于线程main函数的、在单独执行流中执行的函数地址值(函数指针)。 arg--通过第三个参数传递调用函数时包含传递参数信息的变量地址值。 int pthread_join(pthread_t thread, void** status);//p290 thread--该参数值ID的线程终止后才会从该函数返回。 status--保存线程的main函数返回值的指针变量地址值。 root@my_linux:/tcpip# gcc -D_REENTRANT mythread.c -o mthread -lpthread p292 int pthread_detach(pthread_t thread);//p307
线程访问变量num时应该阻止其他线程访问 同步Synchronization
临界区:“函数内同时运行多个线程时引起问题的多条语句构成的代码块。”
互斥量(Mutex,Mutual Exclusion)
pthread_mutex_init() & pthread_mutex_destroy(); //p300
pthread_mutex_lock() & pthread_mutex_unlock();
sem_init() & sem_destroy(); sem_post() & sem_wait(); //p304
第19章 Windows平台下线程的使用
“Windows线程在首次调用的线程main函数返回时销毁(销毁时间点和销毁方法与Linux不同)。还有其他方法可以终止线程,但最好的办法就是让线程main函数终止(返回),故省略其他说明。”
HANDLE CreateThread();//p318 uintptr_t _beginthreadex( //p319 创建“使用线程安全标准C函数”的线程 void* security, unsigned stack_size, unsigned (* start_address)(void*), //传递线程的main函数信息 void* arglist, unsigned initflag, unsigned* thrdaddr //用于保存线程ID的变量地址值 ); //成功时返回线程句柄,失败时返回0
所以终止状态又称“signaled状态”(boolean TRUE),未终止状态称为“non-signaled状态”(FALSE)。
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);//p322 针对单个内核对象验证signaled状态 DWORD WaitForMultipleObjects(DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll, DWORD dwMilliseconds);//p323
第20章 Windows中的线程同步
典型的内核模式同步方法有基于事件(Event)、信号量、互斥量等内核对象的同步
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);//p329 //销毁CRITICAL_SECTION对象使用过的(CRITICAL_SECTION对象相关的)资源 void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection); //获取和释放“钥匙” void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName);//p331 BOOL CloseHandle(HANDLE hObject); WaitForSingleObject(hMutex, INFINITE);//互斥量在WaitForSingleObject函数返回时自动进入non-signaled状态,“auto-reset” //临界区的开始 //...... //临界区的结束 ReleaseMutex(hMutex);//p332 HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName); BOOL ReleaseSemaphore()//p334 释放意味着信号量值的增加 信号量对象的值大于0时成为signaled状态,为0时成为non-signaled状态。 WaitForSingleObject(hSemaphore, INFINITE);//返回的时候同时将信号量值减一 //临界区的开始 //... //临界区的结束 ReleaseSemaphore(hSemaphore, 1, NULL);//释放意味着信号量的增加 事件对象的主要特点是可以创建manual-reset模式的对象 HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);//p337 BOOL ResetEvent(HANDLE hEvent);//to the non-signaled BOOL SetEvent(HANDLE hEvent);//to the signaled
第21章 异步通知I/O模型
异步I/O是指I/O函数的返回时刻与数据收发的完成时刻不一致
“异步方式能够比同步方式更有效地使用CPU” "指定监视对象后可以离开执行其他任务,最后再回来验证状态变化"
int WSAEventSelect();//p347 该函数用于指定某一套接字为事件监视对象 WSAEVENT WSACreateEvent(void);//p348 便捷创建manual-reset模式non-signaled状态的事件对象 BOOL WSACloseEvent(WSAEVENT hEvent); DWORD WSAWaitForMultipleEvents();//p349 发生了事件 int WSAEnumNetworkEvents();//p350-351 !! 区分事件类型
第22章 重叠I/O模型
SOCKET WSASocket();//p358 可以创建适用于重叠I/O的套接字 WSASocket(PF_INET, SOCK_STREAM, 0, NULL, WSA_FLAG_OVERLAPPED);//创建出可以进行重叠I/O的非阻塞模式的套接字 int WSASend();//p359 执行重叠I/O的WSASend函数 BOOL WSAGetOverlappedResult();//p361 int WSARecv(); int WSAGetLastError(void);//p364 WSA_IO_PENDING
Gather/Scatter I/O是指,将多个缓冲中的数据累积到一定程度后一次性传输(Gather输出),将接收的数据分批保存(Scatter输入)。
重叠I/O中有2种方法确认I/O的完成并获取结果。
-
利用WSASend、WSARecv函数的第六个参数,基于事件对象。
-
完成I/O时,WSAOVERLAPPED结构体变量引用的事件对象将变为signaled状态。
-
为了验证I/O的完成和完成结果,需要调用WSAGetOverlappedResult函数。
-
-
利用WSASend、WSARecv函数的第七个参数,基于Completion Routine。//p367
void CALLBACK CompletionROUTINE();//p370 返回值类型void后插入的CALLBACK关键字与main函数中声明的关键字WINAPI相同,都是用于声明函数的调用规范,所以定义Completion Routine时必须添加。
第23章 IOCP
IOCP(Input Output Completion Port,输入输出完成端口)
完成端口对象(Completion Port,简称CP对象)
实现非阻塞模式的套接字
HANDLE CrateIoCompletionPort();//p379-380 创建CP对象、连接完成端口对象和套接字 BOOL GetQueuedCompletionStatus();//p381 确认完成端口已完成的I/O和线程的I/O处理
第一章 网络编程入门
1.1 软件结构
C/S B/S
1.2 网络通信协议
TCP/IP
1.3 协议分类
UDP
TCP
1.4 网络编程三要素
协议
IP地址
ipconfig(configuration--配置)
ping 空格 IP地址
端口号
第二章 TCP通信程序
2.1 概述
2.2 Socket类
套接字:包含了IP地址和端口号的网络单位
客户端:
服务器端: