首页 > 技术文章 > 《TCP/IP网络编程》函数整理

chrysanthemum 2020-11-05 09:31 原文

第一章

主机序与网络序转换函数

#include<netinet/in.h>
uint32_t htonl(uint32_t hosttolong);
uint32_t ntohl(uint32_t nettohostlong);
uint16_t htons(uint16_t hosttoshort);
uint16_t ntohs(uint16_t nettohostshort);

#include<iostream>
#include<netinet/in.h>
#include<string>
using namespace std;
int main()
{
    uint32_t s;
    cin>>s;
    uint32_t hs=htonl(s);
    cout<<hs<<endl;
    cout<<ntohl(hs)<<endl;
    uint16_t S;
    cin>>S;
    uint16_t ss=htons(S);
    cout<<ss<<endl;
    cout<<ntohs(ss)<<endl;
    return 0; 
}

 

 

IP地址十进制与二进制的转换

#include<arp/inet.h>
int_add_t inet_addr(const char* cp);//将点分法IP地址 字符串 转换为 in_addr结构体 中的IP地址格式
int inet_aton(const char* cp,struct in_addr *inp);//将点分法IP地址字符串转换为in_addr结构体中的IP地址格式
char* inet_ntoa(struct in_addr *inp);//将in_addr结构体中的IP地址格式转换为点分法IP地址字符串

#include<iostream>
#include<cstdio>
#include<arpa/inet.h>
#include<cstring>
using namespace std;

//    struct in_addr {
//    in_addr_t s_addr;
//    };
//    表示一个32位的IPv4地址。
//    in_addr_t一般为32位的unsigned int,其字节顺序为网络字节序,
//    即该无符号数采用大端字节序。其中每8位表示一个IP地址中的一个数值。
int main()
{
    char ip1[] = "192.168.0.74";
    printf("IP1: %s\n", ip1);
    
    in_addr addr1;
    unsigned long l1 = inet_addr(ip1); //字符串形式的IP地址 --> 二进制的网络字节序     
    printf("inet_addr:%ld\n",l1);

    char ip2[]="127.0.0.1";
    in_addr addr2;
    inet_aton(ip2,&addr2);// 字符串形式的IP地址 --> 二进制的网络字节序
    printf("inet_aton:%ld\n",addr2.s_addr);
    
    printf("inet_ntoa:%s\n",inet_ntoa(addr2)); //二进制的网络字节序 ->字符串形式的IP地址
    return 0;
}

inet_ntoa的注意点:

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

int main(int argc, char* argv[])
{
    struct in_addr addr1,addr2;
    ulong l1,l2;
    l1= inet_addr("192.168.0.74");
    l2 = inet_addr("211.100.21.179");
    memcpy(&addr1, &l1, 4);
    memcpy(&addr2, &l2, 4);
    printf("%s : %s/n", inet_ntoa(addr1), inet_ntoa(addr2));
    //从这里可以看出,printf里的inet_ntoa只运行了一次。
    //inet_ntoa返回一个char *,而这个char *的空间是在inet_ntoa里面静态分配的
    //所以inet_ntoa后面的调用会覆盖上一次的调用。第一句printf的结果只能说明在printf里面的可变参数的求值是从右到左的,仅此而已。
    //上面的这个问题值得重视。
    printf("%s/n", inet_ntoa(addr1));
    printf("%s/n", inet_ntoa(addr2));
    return 0;
}

 

第二章

BSD Unix套接字API

#include<sys/socket.h>
#include<sys/types.h>

int socket(int dome,int types,int protocol);成功返回一个套接字描述符,不成功-1
    int dome:协议族,TCP——AF_INET|PF_INET
    int types:协议类型,TCP——SOCKET_STREAM|SOCKET_DGREAM ,SOCK_DGRAM|SOCK_STREAMint protocol:协议号,TCP——0

int close(int socketfd);
    int socketfd:套接字描述符

/**客户端**/
int connect(int socketfd,struct socketaddr*server_addr,int len);不成功-1
    int socketfd:套接字描述符
    struct socketaddr*server_addr:服务器的地址信息
    int len:server_addr长度

/**服务器**/
int accept(int socketfd,struct socketaddr*client_addr,const int* len  socklen_t*addrlen);不成功-1
    int socketfd:套接字描述符
    struct socketaddr*client_addr:客户端的地址信息
    int len:client_addr长度

int ssize_t send(int socketfd,const void*buf,intsize_t buf_len,int flag);不成功-1,成功返回实际发送的字节数
    int socketfd:套接字描述符
    const void*buf:发送缓冲区的地址
    int buf_len:发送数据长度
    int flag:为0等同于write

int rev(int socketfd,void*buf,int buf_len,int flag);不成功-1,成功返回实际接受的字节数
    int socketfd:套接字描述符
    const void*buf:接收缓冲区的地址
    int buf_len:接收数据长度
    int flag:为0等同于read

/**服务器**/
int listen(int socketfd,int queuelen);不成功-1
    int socketfd:套接字描述符
    int queuelen:等待队列的长度,大多数系统模认缺省值为20

/**服务器**/ int bind(int socketfd,struct socketaddr*my_addr,int len);不成功-1 int socketfd:套接字描述符 struct socketaddr*my_addr:自己的地址信息 int len:my_addr长度 int sendto(int socketfd,const void* buf,int buf_len,unsigned int flag,struct socketaddr*to_addr,int tolen);不成功-1 int socketfd:套接字描述符 const void* buf:发送信息缓冲区地址 int buf_len:缓冲区长度 int flag:一般为0 struct socketaddr*to_addr:接受方的地址信息 int tolen:to_addr的长度 int revfrom(int socketfd,void* buf,int buf_len,unsigned int flag,struct socketaddr*from_addr,int *fromlen);不成功-1 int socketfd:套接字描述符 const void* buf:接收信息缓冲区地址 int buf_len:缓冲区长度 int flag:一般为0 struct socketaddr*from_addr:发送方的地址信息 int fromlen:from_addr的长度 int shutdown(int socketfd,int howto);不成功-1 int socketfd:套接字描述符 int howto:如何关闭—— 0关闭读,1关闭写,2读写都关闭

其他

#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd,...)
// 对文件上锁的一个通用函数,它还可以改变文件进程各方面的属性
fd:文件描述符
cmd:操作命令,比如设置套接字阻塞非阻塞的命令为F_SETFL
...:cmd的参数。比如设置非阻塞IO型的F_SETFL的参数为O_NONBLOCK 
// example
socketfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(socketfd, F_SETFL, O_NONBLOCK);


#include <sys/types.h>
#include<sys/socket.h>
int getpeername(int sockfd, struct sockaddr* addr, int* addrlen);
// 用于获取所连接的远端机器上的对等方套接字的名称
// 成功将返回0,若出错则返回-1
sockfd:套接字描述符。
addr:指针,用于存放远端机器上的对等方套接字的端点地址。
addrlen:远端地址结构serv_addr的长度,可使用sizeof (struct sockaddr) 来获得。


#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);
// 用于设置与某个套接字关联的选项。
// 成功将返回0,出错则返回-1
sockfd:指明将要被设置选项的套接字描述符。
level:指明选项所在的协议层。操作套接字选项时,应将level的值指定为SOL_SOCKET。
optname:指明需要设置的选项名
optval:指向包含新选项值的缓冲区。该参数的类型将根据选项名称的数据类型进行转换。
optlen:选项值的长度
// example
int rBuf=32*1024;  //设置接收缓冲区为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&rBuf,sizeof(int));
int sBuf=32*1024;  //设置发送缓冲区为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&sBuf,sizeof(int)); 
// 注:当设置TCP套接口接收缓冲区的大小时,函数调用顺序是很重要的,因为TCP的窗口规模选项是在建立连接时用SYN与对方互换得到的。
// 对于客户,SO_RCVBUF选项必须在调用connect()函数之前设置;
// 对于服务器,SO_RCVBUF选项必须在调用listen()函数之前设置。


#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
// 获取与某个套接字关联的选项。成功将返回0,出错-1。
sockfd:指明将要被设置选项的套接字描述符。
level:指明选项所在的协议层。操作套接字选项时,应将level的值指定为SOL_SOCKET。
optname:指明需要设置的选项名
optval:指向包含新选项值的缓冲区。该参数的类型将根据选项名称的数据类型进行转换。
optlen:选项值的长度

Windows套接字API

WinSock包含两个主要的版本,即WinSock1和WinSock2。
在使用WinSock 1.1时,需要引用头文件winsock.h和库文件wsock32.lib
#include<winsock.h> 
#pragma comment(lib, "wsock32.lib") 
 
通常使用WinSock 2.2实现网络通信的功能,则需要引用头文件winsock2.h和库文件ws2_32.lib
#include<winsock2.h> 
#pragma comment(lib, "ws2_32.lib")



#include<windows.h>
#include<winsock.h>
#include<winsock2.h>
#pragma comment(lib,"ws2_32.lib")
WSAStartup();初始化Winsock DLL 

BOOL PASCAL FAR closesocket(int socketfd); 
    int socketfd:套接字描述符 
其他的跟上面的一样




使用WinSock 2.2实现网络通信的应用程序框架
#include<winsock2.h> 
#pragma comment(lib, "ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[]) // 主函数
{   
    WSADATA wsaData; //主要包含了系统所支持的Winsock版本信息 
    
    if( WSAStartup( MAKEWORD(2,2), &wsaData) != 0 )// 初始化Winsock 2.2 
    {   
        printf( "WSAStartup 无法初始化!"); 
        // WSAStartup
        // 第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本
          //第二个参数返回所请求版本的Socket初始化信息数据。
        return 0; 
    } 
    // 使用WinSock实现网络通信
    // ......
    // 最后应该做一些清除工作
    if( WSACleanup() == SOCKET_ERROR ) 
        printf( "WSACleanup 出错!"); 
    return 0;
}

常用的处理函数

#include<stdio.h>
size_t sizeof(type|objname);
    type:类型
    objname:对象
    
#include<string.h>
size_t strlen(const char* str);
    const char* str:字符指针或字符串

#include<stdio.h>
int fprintf(FILE * fp,const char* format[,argv,...]);
    FILE * fp:文件指针
    const char* format:格式化字符串
    [,argv...]:可选的参数

#include<mem.h>
void *memset(void * obj,int c,int len);
    void * obj:要被设置值的内存空间的起始地址
    int c:赋值的内容
    int len:设置的长度
    
#includ<string.h>
int strcmp(const char* s1,const char* s2);

#include<stdlib.h>
int atoi(const char*s);
    const char*s:要被转的字符指针
#include<stdio.h> int main(int argv,const char* argc[]); int argv:参数个数 const char* argc[]:参数列表 #include<time.h> time_t time(time_t *t);获得秒数 time_t *t:存储时间的缓冲区指针 char *ctime(const time_t *t);将秒数转换为字符串 time_t *t:存储时间的缓冲区指针 #include<string.h> int fputs(char * str,FILE *stream); char * str:送入流的字符串指针 FILE *stream:文件指针 #include<string.h> char* strerror(int errornum);返回错误码对应的错误原因字符串 int errornum:错误码

#include<stdarg.h> va_list:该变量主要用来操纵整个可变参数列表 void va_start(va_list ap,argN); 初始化va_list类型的参数ap,并且使得ap指向第一个可选参数 argN:紧邻可变参数的前面一个固定参数
void va_copy(va_list dest,va_list src);用于复制va_list类型的变量
type var_arg(va_list ap,type);返回参数ap所指向的列表中的参数的下一个参数,每一次调用都会修改ap的值,这样就能正确的返回参数列表中的所有参数值 type:存储参数ap所指向的参数的数据类型
void var_end(va_list ap);每次调用va_start()函数和v_copy()函数之后,都要调用va_end()函数来销毁变量ap,即将指针置为NULL
//以下代码保存于文件errexit.c #include <stdarg.h> /*提供C标准库的 va_list 类型和 va_start 、 va_arg 、 va_end 宏的定义*/ #include <stdio.h> /*标准输入输出头文件,包含了标准输入输出函数(如perror和printf等)的定义*/ #include <stdlib.h> /*C标准库头文件,包含了C语言标准库函数(如exit、atoi等)原型的定义*/ int errexit(const char *format,...) { /*定义可变参数函数errexit(),该函数包含一个固定参数format,可变参数用省略号"…"表示*/ va_list args; //声明一个va_list类型的变量args va_start(args, format); /*对变量args进行初始化,使得args指向可变参数列表中的第一个可选参数;format为紧邻可变参数"…"的前面一个固定参数*/ vfprintf(stderr, format, args); /*调用vfprintf()函数向标准出错文件输出一个出错信息*/ va_end(args); //调用va_end()函数来销毁变量args,释放其所占资源 exit(1); }

第三章

进程与线程基础

#include<unistd.h>
int fork();
    返回0:子进程
    返回-1:创建失败
    其他:父进程
    
#include<unistd.h>
pid_t getpid(void);获得自己的进场号
pid_t getppid(void);获得父进程号

#include<stdlib.h>
void exit(int code):
   int code 0:正常退出
   int code 1:程序出错
   int code -1:异常退出
    
#include<sys/typs.h>
#include<sys/wait.h>
void pid_t wait(int *status);成功返回子进程ID,失败返回-1。
    WIFEXITED(status):正常退出返回非零
    WEXITSTATUS(status):正常结束,可以获得由exit()返回的 结束代码
    WIFSIGNALED(status):因为信号而退出,返回非零
    WTERMSIG(status):因为信号结束,获得子进程的 中止信号代码
    WIFSTOPPED(status):因为暂停执行,返回非零
    WSTOPSIG(status):处于暂停状态,获得引发暂停的 暂停信号代码
    
#include<sys/typs.h>
#include<sys/wait.h>
void pit_t waitpid(int pid_t pid,int *status,int option);
    pid >0 :等待的指定子进程
        =-1:等同于wait
        =0 :等待同一个进程组中的任何子进程
        <-1:等待一个指定进程组中的任何子进程,ID为pid绝对值
    int *status:保存被收集进程退出时的一些状态
    int option:控制waitpid,0|WNOHANG|WUNTRACED
        WUNTRACED:当子进程处于暂停状态,waitpid()将马上返回
        WNOHANG:即使没有子进程退出,waitpid()也将立即返回
        0:会像wait()那样阻塞父进程,直到所等待的子进程退出
    (1)正常返回:返回收集到的子进程的进程ID;
    (2)如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
    (3)如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在

#include<signal.h>
void (*signal(int signum,void (*hander)(int)))(int);
  int signum:指明函数所要处理的信号编号;
  handler:描述了与信号关联的动作,可取以下三种值
    函数地址:该函数必须在signal()函数被调用之前申明,当接收到一个信号编号为signum的信号时,进程就执行handler 所指定的函数。
      SIGIGN:这个符号表示忽略信号编号为signum的信号。
      SIGDFL:这个符号表示恢复系统对信号的默认处理。

#include<pthread.h>
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine) (void *), void * arg);
    pthread_t *thread:线程标识符
    pthread_attr_t *attr:用于标识线程的运行属性
    void *(*start_routine) (void *):指向线程的线程体函数,指明该线程所执行的操作
    void * arg:是用于传递给线程体函数的参数

#include <pthread.h> //初始化/去除初始化的函数
int pthread_attr_init(pthread_attr_t *attr);//
int pthread_attr_destroy(pthread_attr_t *attr);
    pthread_attr_t *attr:是pthread_attr_t结构体指针

#include <pthread.h> ////设置/获取线线程的分离状态
int pthread_attr_getdetachstate(const pthread_attr_t * attr, int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
    pthread_attr_t *attr:是pthread_attr_t结构体指针
    int detachstate:线程的分离状态属性
        PTHREAD_CREATE_DETACHED:该线程将以分离状态运行;
        PTHREAD_CREATE_JOINABLE:则该线程将以默认的非分离状态运行
        
#include <pthread.h> //设置/获取线程的继承性
int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched);
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
    pthread_attr_t *attr:是pthread_attr_t结构体指针
    inheritsched:线程的继承性
        PTHREAD_INHERIT_SCHED:表示新线程将继承创建自己的父线程的调度策略和参数
        PTHREAD_EXPLICIT_SCHED:表示使用在schedpolicy和schedparam属性中显式设置的调度策略和参数
        
#include <pthread.h> //设置/获取线程的调度策略
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
    pthread_attr_t *attr:是pthread_attr_t结构体指针
    int policy:为线程的调度策略,主要包括先进先出(SCHED_FIFO)、轮循(SCHED_RR)等。

#include <pthread.h> //设置/获取线程的调度参数
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
    pthread_attr_t *attr:是pthread_attr_t结构体指针
    struct sched_param *param:是sched_param结构体指针

#include <pthread.h> //获得系统支持的最大和最小线程优先权值,失败将返回-1
int sched_get_priority_max(int policy);
int sched_get_priority_min(int policy);

#include <pthread.h> //设置/获取线程的作用域
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
int pthread_attr_getscope(const pthread_attr_t *attr, int *scope);
    pthread_attr_t *attr:是pthread_attr_t结构体指针
    int scope:是作用域或指向作用域的指针
    
#include <pthread.h> //设置/获取线程堆栈保护区的大小
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t *guardsize);
    pthread_attr_t *attr:是pthread_attr_t结构体指针
    size_t *guardsize:控制着线程堆栈末尾之后以避免堆栈溢出的堆栈保护区(扩展内存)的大小。

#include <pthread.h> //设置/获取线程堆栈的地址
int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr);
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
    pthread_attr_t *attr:是pthread_attr_t结构体指针
    stackaddr:线程的堆栈地址。

#include <pthread.h> //设置/获取线程堆栈的大小
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr , size_t *stacksize);
    pthread_attr_t *attr:是pthread_attr_t结构体指针
    stacksize:线程的堆栈地址
    
#include <pthread.h> //提供线程函数原型和数据结构的定义
int pthread_exit(void * value_ptr);
    value_ptr:线程返回值指针,该返回值将被传给另一个线程,另一个线程则可通过调用pthread_join()函数来获得该值。
    
#include <pthread.h> //提供线程函数原型和数据结构的定义
int pthread_join(pthread_t thread,void **value_ptr);
    pthread_t thread:等待终止的线程的线程标识符
    void **value_ptr:函数返回值存储在该指针指向的位置
    该返回值可以是由pthread_exit()给出的值,或者该线程被取消而返回PTHREAD_CANCELED。

#include <pthread.h> /*提供线程函数原型和数据结构的定义*/
pthread_t pthread_self();

    

第五章

锁机制

定义互斥锁变量:pthread_mutex_t;
定义互斥锁属性变量:pthread_mutexattr_t;




初始化互斥锁属性变量:pthread_mutexattr_init();
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict mutexattr);
mutex:指向要初始化的互斥锁的指针。
mutexattr:指向互斥锁属性对象的指针,该属性对象定义要初始化的互斥锁的属性。
                如果该指针设置为 NULL,则表示使用默认的属性,默认属性为快速互斥锁。




初始化互斥锁变量:pthread_mutex_init();
#include <pthread.h> 
int pthread_mutexattr_init(pthread_mutexattr_t *mattr);
mattr:指向互斥锁属性对象的指针。



获得/设置互斥锁的类型属性:pthread_mutexattr_settype、pthread_mutexattr_gettype
#include <pthread.h> //提供了线程函数原型和数据结构的定义
int pthread_mutexattr_settype(pthread_mutexattr_t *mattr, int type);
int pthread_mutexattr_gettype(pthread_mutexattr_t *mattr, int *type);
mattr:指向互斥锁属性对象的指针。
type:互斥锁的类型属性,主要包括如下几种:
   (1)PTHREAD_MUTEX_NORMAL:快速互斥锁。
   (2)PTHREAD_MUTEX_RECURSIVE:递归互斥锁。
   (3)PTHREAD_MUTEX_ERRORCHECK:检错互斥锁。



获得/设置互斥锁属性对象的共享属性:pthread_mutexattr_setpshared、pthread_mutexattr_getpshared
#include <pthread.h> //提供了线程函数原型和数据结构的定义
int pthread_mutexattrattr_getpshared(const pthread_attr_t *mattr, int pshared);
int pthread_mutexattrattr_setpshared(const pthread_attr_t *mattr, int *pshared);
mattr:指向互斥锁属性对象的指针。
pshared:互斥锁属性对象的共享属性,该参数只有两个取值,分别为和。
        PTHREAD_PROCESS_SHARED:在多个进程中的线程之间共享该互斥锁,
        PTHREAD_PROCESS_PRIVATE:仅在那些由同一个进程所创建的线程之间共享该互斥锁。 



回收互斥锁属性对象:pthread_mutexattr_destroy()
成功将返回0,若失败则返回错误编号
#include <pthread.h> //提供了线程函数原型和数据结构的定义
int pthread_mutexattrattr_destroy( pthread_mutexattr_t *mattr );
mattr:指向互斥锁属性对象的指针。 



互斥锁上锁:pthread_mutex_lock();
调用成功将返回0,若失败则返回错误编号。
#include <pthread.h> //提供了线程函数原型和数据结构的定义
int pthread_mutex_lock(pthread_mutex_t *mutex);
mutex:指向要加锁的互斥锁的指针。



互斥锁判断上锁:pthread_mutex_trylock();
调用成功,则在获得了互斥锁后将返回0,若失败则将返回一个错误编号
#include <pthread.h> //提供了线程函数原型和数据结构的定义
int pthread_mutex_trylock(pthread_mutex_t *mutex);
mutex:指向要加锁的互斥锁的指针。 




互斥锁解锁:pthread_mutex_unlock();
调用成功将返回0,若失败则将返回一个错误编号。
#include <pthread.h> //提供了线程函数原型和数据结构的定义
int pthread_mutex_unlock(pthread_mutex_t *mutex);
mutex:指向要解锁的互斥锁的指针



消除互斥锁:pthread_mutex_destroy()
调用成功将返回0,若失败则将返回一个错误编号
#include <pthread.h> //提供了线程函数原型和数据结构的定义
int pthread_mutex_destroy(pthread_mutex_t *mutex);
mutex:指向待释放的互斥锁的指针。

综合实列

#include<stdio.h>    /* 标准输入输出头文件,包含了标准输入输出函数(如perror和printf等)的定义*/
#include<unistd.h>   /* LINUX标准头文件,包含了各种LINUX系统服务函数原型和数据结构的定义*/
#include<stdlib.h>   /* C标准库头文件,包含了C语言标准库函数(如exit、atoi等)原型的定义*/
#include<string.h>   //提供了字符串函数原型的定义
#include<pthread.h>  //提供了线程函数原型和数据结构的定义
#include<semaphore.h>  //提供了信号量函数原型和数据结构的定义
void *thread_function(void* arg);//声明线程体函数
pthread_mutex_t work_mutex;//定义互斥锁变量
#define WORK_SIZE 1024//定义符号常量,用作指定缓存区大小
char work_area[WORK_SIZE];//定义全局变量,用于缓存用户键盘输入数据
int time_to_exit = 0; //定义全局变量,用作循环结束标志符
int main() 
{ 
    int res;  //定义局部变量,用于存储函数调用后的返回值
    pthread_t a_thread;  //定义局部变量,用于存储所创建的新线程的标识符
    void *thread_result; /*定义局部指针变量,用于存储调用pthread_join()函数等待从线程结束时被等待线程的返回值*/

    res = pthread_mutex_init(&work_mutex, NULL); //调用pthread_mutex_init()函数对互斥锁进行初始化
    if (res != 0)
    { /*若互斥锁初始化失败则调用perror()函数提示出错信息,然后调用exit()函数退出程序*/
          perror("Mutex initialization failed"); 
          exit(EXIT_FAILURE); 
    } 

    res = pthread_create(&a_thread, NULL, thread_function, NULL); //调用pthread_create()函数创建一个新线程
    if (res != 0) 
    {    /*若新线程创建失败则调用perror()函数提示出错信息,然后调用exit()函数退出程序*/
        perror("Thread creation failed"); 
        exit(EXIT_FAILURE); 
    } 

    pthread_mutex_lock(&work_mutex); /*由于需要对全局变量work_area进行写操作,因此主线程须先调用pthread_mutex_lock()函数对互斥锁进行加锁*/
    printf("Input some text. Enter 'end' to finish\n"); /*主线程调用printf()函数提示用户键盘输入数据*/
    while(!time_to_exit) 
    {  /*若time_to_exit等于0,即表示用户键盘输入尚未结束,time_to_exit的值由从线程修改更新*/
        fgets(work_area, WORK_SIZE, stdin); 
        /*调用fgets()函数从标准输入设备stdin中读入WORK_SIZE-1个字符放入work_area缓存区,
        如果在未读满WORK_SIZE-1个字符之时,已读到一个换行符或一个EOF(文件结束标志),
        则结束本次读操作,读入的字符串中最后包含读到的换行符。
        读入结束后,系统将自动在最后添加'\0'字符作为行结束符*/
        pthread_mutex_unlock(&work_mutex);  //调用pthread_mutex_unlock()函数对互斥锁进行解锁
        while(1) 
        { /*反复判断用户键盘输入数据是否已经由从线程输出到标准输出设备。
        由于若从线程已将用户键盘输入数据输出到标准输出设备,则从线程将会把work_area缓存区清空,
        故只需反复判断work_area缓存区是否为空,即可判定用户键盘输入数据是否已由从线程输出到了标准输出设备*/
            pthread_mutex_lock(&work_mutex);
            /*由于需要对全局变量work_area进行读操作,
            因此须先调用pthread_mutex_lock()函数对互斥锁进行加锁*/
            if (work_area[0] != '\0') 
            { /*若work_area[0] != '\0',表示从线程尚未把work_area缓存区清空,即用户键盘输入数据尚未由从线程输出到标准输出设备*/
                pthread_mutex_unlock(&work_mutex); //调用pthread_mutex_unlock()函数对互斥锁进行解锁
                sleep(1); /*调用sleep()函数让主线程休眠1秒,从而让从线程获得互斥锁,以便从线程将用户键盘输入数据输出到标准输出设备上*/
            } 
            else 
            { /*若time_to_exit等于1,即表示用户键盘输入结束,调用break 退出while循环*/
                break; 
            } 
        } //end while(1)
    } //end while(!time_to_exit) 
    /*若time_to_exit等于1,即表示用户键盘输入已经结束,此时执行以下代码段*/
    pthread_mutex_unlock(&work_mutex); /*调用pthread_mutex_unlock()函数对互斥锁进行解锁*/
    printf("\nWaiting for thread to finish...\n"); /*调用printf()函数提示用户等待从线程结束*/
    res = pthread_join(a_thread, &thread_result); 
    /*调用pthread_join()函数让主线程等待从线程结束,主线程会一直等待直到等待的线程结束自己才结束;
    若不调用pthread_join()函数,则主线程会很快结束从而使整个进程结束,此时可能从线程还未来得及将用户的键盘输入数据输出到标准输出设备*/
    if (res != 0) 
    { /*若pthread_join()调用失败,则调用perror()函数提示出错信息,然后调用exit()函数退出程序*/
        perror("Thread join failed"); 
        exit(EXIT_FAILURE); 
    } 
    printf("Thread joined\n");//从线程退出则显示提示信息
    pthread_mutex_destroy(&work_mutex);/*调用 pthread_mutex_destroy()函数释放互斥锁*/
    exit(EXIT_SUCCESS); //调用exit()正常退出程序
} 
void *thread_function(void *arg) 
{ //从线程执行体函数
    sleep(1); //从线程先休眠,让主线程先执行,以便获取用户键盘输入
    pthread_mutex_lock(&work_mutex);
    /*从线程用于将用户的键盘输入数据输出到标准输出设备,
    由于需要对全局变量work_area进行读操作,
    因此从线程须先调用pthread_mutex_lock()函数对互斥锁进行加锁*/
    while(strncmp("end", work_area, 3) != 0) 
    {  
         /*若work_area中包含的字符串不为"end"字符串,表示用户键盘输入尚未结束。
         显然,需要注意的是,若用户在一次输入中包含了"end"字符串,
         此时并不表示用户键盘输入已经结束;而只有在一次输入中用户仅仅输入了"end"字符串时,才表示用户键盘输入已经结束*/
        printf("You input %d characters\n", strlen(work_area) -1);  /*调用printf()与strlen()函数统计并显示用户本次输入的字符个数*/
        work_area[0] = '\0';  //清空缓存区work_area
        pthread_mutex_unlock(&work_mutex); /*调用pthread_mutex_unlock()函数对互斥锁进行解锁*/
        sleep(1);/*从线程休眠,从而让主线程执行并获得互斥锁,以便再次获取用户的键盘输入*/
        pthread_mutex_lock(&work_mutex);
        /*由于以下需要通过检查work_area是否为空来获知用户的键盘输入过程是否已经结束,
        因此,从线程须先调用pthread_mutex_lock()函数对互斥锁进行加锁*/
        while (work_area[0] == '\0' ) 
        { 
            //反复判定work_area是否为空
            pthread_mutex_unlock(&work_mutex); /*若work_area为空,则调用pthread_mutex_unlock()函数对互斥锁进行解锁*/
            sleep(1); /*然后,让从线程休眠1秒,从而让主线程执行并获得互斥锁,以便再次获取用户的键盘输入*/
            pthread_mutex_lock(&work_mutex);//调用pthread_mutex_lock()函数对互斥锁进行加锁
      } 
    }/*若work_area中包含的字符串等于"end"字符串,则执行以下代码段*/
    time_to_exit = 1; /*设置 time_to_exit = 0,使得主线程可跳while(!time_to_exit)循环*/
    work_area[0] = '\0';//清空缓存区work_area
    pthread_mutex_unlock(&work_mutex);//调用pthread_mutex_unlock()函数对互斥锁进行解锁
    pthread_exit(0); //调用pthread_exit()让从线程正常退出
} 
 

无名信号量

sem_init():初始化一个信号量
#include <semaphore.h>  //提供信号量函数原型和数据结构的定义
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem:指向信号量结构的指针。
pshared:决定信号量能否在几个进程间共享。
    !=0:在进程间共享,
    ==0:在当前进程的所有线程共享。
value:信号量初始化值。
    通常会初始化为可用资源的数目

    
    
sem_wait():用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减1,表明公共资源经使用后减少。
成功将返回0,若出错则返回-1
#include <semaphore.h>  //提供信号量函数原型和数据结构的定义
int sem_wait(sem_t * sem);
sem:指向信号量结构的指针。



sem_trywait():函数sem_wait()的非阻塞版本,如果信号灯计数大于0,则将信号量sem的值减1并返回0,否则立即返回-1
#include <semaphore.h>  //提供了信号量函数原型和数据结构的定义
int sem_trywait(sem_t * sem);
sem:指向信号量结构的指针。



sem_post():将信号量sem的值增加1。表示增加了一个可访问的资源。
#include <semaphore.h>  //提供了信号量函数原型和数据结构的定义
int sem_ post(sem_t * sem);
sem:指向信号量结构的指针


sem_ getvalue():读取sem中的信号灯计数
成功将返回0,若出错则返回-1,
#include <semaphore.h>  //提供了信号量函数原型和数据结构的定义
int sem_getvalue(sem_t * sem, int * sval);
sem:指向信号量结构的指针。
sval:用于存储读取到的sem中的信号灯计数值


sem_destroy():销毁信号量,归还自己所占用的一切资源。
成功将返回0,若出错则返回-1,
#include <semaphore.h>  //提供了信号量函数原型和数据结构的定义
int sem_destroy(sem_t * sem);
sem:指向信号量结构的指针

无名信号案例1:两个线程将通过随机竞争以获取信号量资源

#include <pthread.h>  //提供了线程函数原型和数据结构的定义
#include <semaphore.h>  //提供信号量函数原型和数据结构的定义
#include <sys/types.h> //基本数据类型头文件,含有基本系统数据类型的定义
#include <stdio.h>  /*标准输入输出头文件,包含了标准输入输出函数(如perror和printf等)的定义*/
#include <unistd.h>  /* LINUX标准头文件,包含了各种LINUX系统服务函数原型和数据结构的定义*/
int number; //定义全局变量
sem_t sem_id; //定义无名信号量变量
void* thread_one_fun(void *arg) 
{ //从线程1执行体函数
    sem_wait(&sem_id);//阻塞线程1直到信号量sem_id的值大于0
    printf("The thread_one has the semaphore\n"); //提示线程1获得信号量
    number++; //全局变量number加1
    printf("number=%d\n",number); //输出全局变量number的值
    sem_post(&sem_id); //释放信号量sem_id
}
void* thread_two_fun(void *arg) 
{ //从线程2执行体函数
    sem_wait(&sem_id);//阻塞线程2直到信号量 sem_id的值大于0
    printf("The thread_two has the semaphore\n");//提示线程2获得信号量
    number--; //全局变量number减1
    printf("number=%d\n", number); //输出全局变量number的值
    sem_post(&sem_id); //释放信号量sem_id
}
int main(int argc, char *argv[]) 
{
    number =1;
    pthread_t pid1, pid2; //定义线程描述符变量
    sem_init(&sem_id, 0,1); /*无名信号量用于多线程间的同步,设置无名信号量的初始值为1*/
    pthread_create(&pid1, NULL,thread_one_fun, NULL);  //创建从线程1
    pthread_create(&pid2, NULL,thread_two_fun, NULL); //创建从线程2
    pthread_join(pid1,NULL); //主线程等待从线程1结束
    pthread_join(pid2,NULL); //主线程等待从线程2结束
    printf("main…\n");
    return 0;
}

无名信号案例2:两个进程(父子进程)中的同步应用

#include <pthread.h>  //提供了线程函数原型和数据结构的定义
#include <semaphore.h>  //提供了信号量函数原型和数据结构的定义
#include <sys/types.h>  //基本数据类型头文件,含有基本系统数据类型的定义
#include <stdio.h>  /*标准输入输出头文件,包含了标准输入输出函数(如perror和printf等)的定义*/
#include <unistd.h> /* LINUX标准头文件,包含了各种LINUX系统服务函数原型和数据结构的定义*/
#include <stdlib.h>
int main(int argc, char *argv[])
{
    int i, number =1,nloop=3; //定义两个局部变量
    sem_t sem_id; //定义无名信号量变量
    sem_init(&sem_id, 1,1); /*无名信号量用于父子进程间的同步,设置无名信号量的初始值为1*/
    if(fork()==0)
    { //在子进程中执行以下for循环代码段
        for (i=0;i<nloop;i++)
        {
            sem_wait(&sem_id); //阻塞从进程直到信号量sem_id的值大于0
            printf("The son_process has the semaphore\n"); //提示从进程获得信号量
            number++; //全局变量number加1
            printf("number=%d\n",number);//输出全局变量number的值
            sem_post(&sem_id); //释放信号量sem_id
        }
        exit(0); //退出子进程
    }
    //在父进程中执行以下for循环代码段
    for(i=0;i<nloop;i++)
    {
        sem_wait(&sem_id);  //阻塞父进程直到信号量sem_id的值大于0
        printf("The father_process has the semaphore\n");//提示父进程获得信号量
        number--; //全局变量number减1
        printf("number=%d\n", number);//输出全局变量number的值
       sem_post(&sem_id); //释放信号量sem_id
    }
    exit(0); //退出父进程
}

有名信号量

sem_open():创建一个新的有名信号量 / 打开一个已存在的有名信号量
成功时返回指向该有名信号量的指针,出错则返回SEM_FAILED
#include <semaphore.h> //提供了信号量函数原型和数据结构的定义
sem_t *sem_open(const char *name,int oflag, mode_t mode, unsigned int value);
name:信号量的外部名字。
oflag:有O_CREAT|EXCL和O_CREAT两个选项
    O_CREAT:当name指定的信号量不存在时会创建一个(要求mode和value必须有效)
            当name指定的信号量存在时则直接打开该信号量(忽略mode和value)
    O_CREAT|EXCL:当name指定的信号量不存在时与选用O_CREAT时功能相同
                 当name指定的信号量存在时则将返回error。
mode:控制新的信号量的权限。
value:信号量初始值。


sem_close(),sem_unlink()
sem_close():关闭sem信号量,并释放资源。
sem_unlink():在所有进程都关闭了信号量之后删除指定的信号量。若调用成功则返回0,否则返回-1
#include <semaphore.h> //提供了信号量函数原型和数据结构的定义
int sem_close(sem_t *sem);
int sem_unlink(const char *name);
sem:指向待关闭信号量的指针。
name:信号量的外部名字

有名信号量案例1:采用循环方法建立5个从线程,然后让它们调用同一个线程处理函数thread_function(),在该函数中利用有名信号量来限制访问共享资源的线程数。共享资源用print()函数来代表,而在真正编程中有可能是个终端设备(如打印机)或是一段有实际意义的代码。

#include <pthread.h>  //提供了线程函数原型和数据结构的定义
#include <semaphore.h>  //提供信号量函数原型和数据结构的定义
#include <sys/types.h>  //基本数据类型头文件,含有基本系统数据类型的定义
#include <stdio.h>  /*标准输入输出头文件,包含了标准输入输出函数(如perror和printf等)的定义*/
#include <unistd.h> /* LINUX标准头文件,包含了各种LINUX系统服务函数原型和数据结构的定义*/
#include <stdlib.h>
#include <fcntl.h>//O_CREAT的头文件
void *thread_function(void *arg); //声明线程执行体函数
void print(void); //声明共享资源函数
sem_t *bin_sem; //定义有名信号量变量
int val; //定义信号量的当前值变量
char sem_name[]= "SEM_NAME";  //定义存储有名信号量外部名字的数组变量并赋值 
int main()
{
    pthread_t a_thread[6]; //定义线程描述符数组变量
    bin_sem = sem_open(sem_name,O_CREAT,0644,3); /*主线程调用sem_open()函数创建有名信号量bin_sem,信号量初始值大小设为3*/
    if(bin_sem== SEM_FAILED) 
    { /*若创建有名信号量bin_sem失败,则调用perror()函数提示出错信息,并调用sem_unlink()函数删除该信号量,然后调用exit()函数退出程序*/
        perror("unable to create semaphore");
        sem_unlink(sem_name);
        exit(-1);
    }
    
    int n=0; //定义用于记录循环次数的计数变量,初始值设为0
    while(n<5) 
    { //循环创建5个从线程
         //将循环次数计数变量加1
        if((pthread_create(&a_thread[n],NULL,thread_function,NULL))!=0) 
        { 
            /*调用pthread_create()函数创建从线程,若失败则调用  perror()函数提示出错信息,然后调用exit()函数退出程序*/
            perror("Thread creation failed");
            exit(1);
        }
        
        pthread_join(a_thread[n],NULL);
        n++; 
        //主线程调用pthread_join()函数等待子线程结束
    }
    sem_close(bin_sem); //主线程调用sem_close()函数释放该信号量
    sem_unlink(sem_name); //主线程调用sem_unlink()删除该信号量
}
void *thread_function(void *arg)
{ //从线程执行体函数
    sem_wait(bin_sem);//阻塞从线程直到信号量bin_sem的值大于0
    print();//从线程获得信号量并执行共享资源函数print()
    sleep(1);//从线程调用sleep()函数休眠1秒,以等待其他线程执行
    sem_post(bin_sem); //当执行任务完毕,从线程释放信号量
    printf("I finished,my pid is %ld\n", getpid());//从线程输出完成提示信息
    pthread_exit(arg); //从线程正常结束返回参数arg给主线程
}
void print()
{ //从线程的共享资源函数
    sem_getvalue(bin_sem,&val);  //获取信号量的当前值
    printf("\tI get it,my tid is %ld\n", pthread_self());//从线程输出获得信号量的提示信息
    printf("\tNow the value have %d\n",val); //从线程输出当前信号量的值
}

 条件变量

使用条件变量的基本过程如下:
►声明一个pthread_cond_t条件变量,并调用pthread_cond_init()函数对其进行初始化。
►声明一个pthread_mutex_t互斥锁变量,并调用pthread_mutex_init()函数对其进行初始化。
►调用pthread_cond_signal()函数发出信号。如果此时有线程在等待该信号,那么该线程将会唤醒。如果没有,则该信号就会被忽略。如果想唤醒所有等待该信号的线程,则调用pthread_cond_broadcast()函数。
►调用pthread_cond_wait()/pthread_cond_timedwait()等待信号。如果没有信号,线程将会阻塞,直到有信号。该函数的第一个参数是条件变量,第二个参数是一个mutex。在调用该函数前必须先获得互斥量。如果线程阻塞,互斥量将立刻会被释放。
►调用pthread_cond_destroy()销毁条件变量,释放其所占用的资源。

案例

#include <pthread.h>  //提供了线程函数原型和数据结构的定义
#include <stdio.h> /*标准输入输出头文件,包含了标准输入输出函数(如perror和printf等)的定义*/
#include <stdlib.h> /*C标准库头文件,包含了C语言标准库函数(如exit、atoi等)原型的定义*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //初始化条件变量
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; //初始化互斥锁

void *thread1(void *); //声明线程t_a的线程体函数
void *thread2(void *); //声明线程t_b的线程体函数
int i=1; //定义用于记录循环次数的计数变量,初始值设为0
int main(void)
{
    pthread_t t_a; //定义线程标识符t_a
    pthread_t t_b; //定义线程标识符t_b
    pthread_create(&t_a,NULL,thread1,(void *)NULL); //创建从线程t_a
    pthread_create(&t_b,NULL,thread2,(void *)NULL); //创建从线程t_b
    pthread_join(t_a, NULL);//等待从线程t_a结束
    pthread_join(t_b, NULL);//等待从线程t_b结束
    pthread_mutex_destroy(&mutex); //释放互斥锁
    pthread_cond_destroy(&cond); //释放条件变量
    exit(0); //程序正常退出
}
void *thread1(void *junk)
{  //线程t_a的线程体函数
    for(i=1;i<=9;i++)
    {
        pthread_mutex_lock(&mutex); //在等待条件成立之前,须先上锁
        if(i%3==0) //设置等待条件,当i为3的倍数时执行以下语句
            pthread_cond_signal(&cond); /*若条件成立则发送信号,以激活某个在该条件变量上阻塞的线程*/
        else //当i不为3的倍数时执行以下语句
            printf("thead1:%d\n",i); //线程t_a执行打印任务
        pthread_mutex_unlock(&mutex); //在执行完任务后,须解锁
        sleep(1); //线程t_b休眠1秒,以便让其他线程执行
   }
}
void *thread2(void *junk)
{  //线程t_b的线程体函数
    while(i<9)
    {
        pthread_mutex_lock(&mutex);//在等待条件成立之前,须先上锁
        if(i%3!=0) //设置等待条件,当i不为3的倍数时
            pthread_cond_wait(&cond,&mutex); //等待上述条件成立
        printf("thread2:%d\n",i); /*当条件成立则执行打印任务*/
        pthread_mutex_unlock(&mutex);/*在执行完打印任务之后需解锁*/
        sleep(1);
   }
}

多线程的并发的TCP服务器案例

 服务器

#include <stdio.h> /*标准输入输出头文件,包含了标准输入输出函数(如perror和printf等)的定义*/
#include <string.h> //提供了字符串函数原型的定义
#include <unistd.h> /* LINUX标准头文件,包含了各种LINUX系统服务函数原型和数据结构的定义*/
#include <sys/types.h> //基本数据类型头文件,含有基本系统数据类型的定义
#include <sys/socket.h> //提供了套接字函数原型与数据结构的定义
#include <netinet/in.h>  //包含了数据结构sockaddr_in的定义
#include <arpa/inet.h> //包含了IP地址转换函数原型的定义
#include <pthread.h>  //提供了线程函数原型与数据结构的定义
#include <stdlib.h>  /*C标准库头文件,包含了C语言标准库函数(如exit、atoi等)原型的定义*/
#define PORT 1234 //定义服务器端口
#define BACKLOG 5  //定义listen队列等待的连接数
#define MAXSIZE 1024 //定义缓冲区大小
void process_cli(int connectfd, struct sockaddr_in client);//声明客户端请求处理函数
void* start_routine(void* arg); //声明线程体函数

typedef struct ARG 
{ //定义用于存储客户端信息的结构体
    int connfd; //用于记录处理该客户连接的套接字描述符
    struct sockaddr_in client; //用于记录客户的端点地址
}ARG;  

int main()
{
    int listenfd, connectfd;  //定义主、从套接字描述符变量
    pthread_t  thread;  //定义线程描述符变量
    ARG *arg;  //定义用于存储客户端信息的结构体变量
    struct sockaddr_in server;  //定义服务器端点地址结构体变量
    struct sockaddr_in client;  //定义客户端点地址结构体变量
    int sin_size; //定义端点地址结构体长度变量
    
    if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
    { /*调用socket()函数创建用于监听客户连接请求到达的主套接字,若失败则调用perror()函数提示出错信息,然后调用exit()函数出错退出程序*/
        perror("Creating socket failed."); 
        exit(1);
    }

    int opt = SO_REUSEADDR; //定义socket属性变量
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); /*设置socket属性,端口可以重用*/
    //以下代码段用于初始化服务器地址结构体变量
    bzero(&server,sizeof(server)); //清空服务器端点地址结构体变量
    server.sin_family=AF_INET; //为服务器端点地址结构体变量赋值协议族
    server.sin_port=htons(PORT); //为服务器端点地址结构体变量赋值端口号
    server.sin_addr.s_addr = htonl (INADDR_ANY); 
    //为服务器端点地址结构体变量赋值IP地址
    
    if(bind(listenfd, (struct sockaddr *)&server,sizeof(struct sockaddr))==-1) 
    { /*调用bind()函数绑定服务器端点地址,若失败则调用perror()函数提示出错信息,然后调用exit()函数出错退出程序*/
        perror("Bind error.");
        exit(1);
    }
    
    if(listen(listenfd,BACKLOG) == -1)
    { /*调用listen()函数设置队列长度并开始监听客户连接请求的到达,若失败则调用perror()函数提示出错信息,然后调用exit()函数出错退出程序*/
        perror("listen() error\n");
        exit(1);
    }
    
    sin_size=sizeof(struct sockaddr_in); //获得端点地址结构体的长度
    while(1)
    {
        if((connectfd=accept(listenfd, (struct sockaddr *)&client, (socklen_t *) & sin_size))==-1) 
        {  /*调用accept()函数建立与客户端的连接并返回表示该连接的从套接字描述符,若失败则调用perror()函数提示出错信息,然后调用exit()函数出错退出程序*/
            perror("accept() error\n");
            exit(1);
        }
        arg = new ARG(); //初始化用于存储客户端信息的结构体变量arg
        arg->connfd = connectfd; //对用于存储客户端信息的结构体变量arg赋值
        memcpy(&arg->client, &client, sizeof(client)); //对用于存储客户端信息的结构体变量arg赋值
        if(pthread_create(&thread, NULL, start_routine, (void*)arg))
        {
            /*以客户端连接为参数,start_routine为线程体函数来创建新线程,若失败则调用perror()函数提示出错信息,然后调用exit()函数出错退出程序*/
            perror("Pthread_create() error");
            exit(1);
        }
    }
    close(listenfd);  //关闭主套接字
}

void* start_routine(void* arg)
{ //线程体函数
    ARG *info; //定义用于存储客户端信息的结构体指针变量
    info = (ARG *)arg; //给结构体指针变量info赋值
    process_cli(info->connfd, info->client); /*在线程体函数中调用process_cli()函数来执行具体任务*/
    free(info); //删除结构体指针变量,释放其所占用的资源
    pthread_exit(NULL); //从线程退出
} 

void process_cli(int connectfd,struct sockaddr_in client)
{
    int num; //定义用于记录recv()函数的返回值的变量
    char recvbuf[MAXSIZE], sendbuf[MAXSIZE], cli_name[MAXSIZE]; /*定义三个缓存区变量,分别用于缓存接收到的数据、要发送的数据和客户端的名字信息*/
    printf("You got a connection from %s.",inet_ntoa(client.sin_addr) ); //输出提示信息
    num = recv(connectfd, cli_name, MAXSIZE,0); /*调用recv()函数接收来自客户端的名字信息*/
    if (num == 0) 
    { /*若没有从客户端接收到名字信息,则调用close()函数关闭对应的套接字,终止与该客户端之间的连接*/
        close(connectfd);
        printf("Client disconnected.\n");
        return;
    }
    //若从客户端接收到了名字信息,则执行以下语句
    cli_name[num - 1] = '\0'; //在cli_name字符串末尾加上字符串结束符'\0'
    printf("Client's name is %s.\n",cli_name); //显示客户端的名字信息
    while (num = recv(connectfd, recvbuf, MAXSIZE,0)) 
    { /*调用recv()函数接收来自客户端的其他数据*/
        recvbuf[num-1] = '\0'; //在recvbuf字符串末尾加上字符串结束符'\0'
        printf("Received client( %s ) message: %s",cli_name, recvbuf);
        //显示收到的客户端的数据
        for (int i = 0; i < num - 1; i++) 
        {  /*将recvbuf 中的字符串反转存入到sendbuf 之中*/
            sendbuf[i] = recvbuf[num - i -2]; 
        }
        sendbuf[num - 1] = '\0'; //在sendbuf字符串末尾加上字符串结束符'\0'
        send(connectfd,sendbuf,strlen(sendbuf),0); /*调用send()函数将反转后的字符串回送给客户端*/
    }
    close(connectfd); /*任务结束,从线程调用close()函数关闭对应的套接字,终止与该客户端之间的连接*/
}

客户端

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
/*客户端*/
int main()
{
    int ssock=socket(AF_INET,SOCK_STREAM,0);
    if(ssock<0)
    {
        printf("创建失败");
        exit(-1);
    }
    struct sockaddr_in client;
    client.sin_addr.s_addr=inet_addr("127.0.0.1");
    client.sin_family=AF_INET;
    client.sin_port=htons(1234);

    if(connect(ssock,(struct sockaddr*)&client,sizeof(client))<0)
    {
        printf("建立连接失败");
        exit(-1);
    }
    close(ssock);
    return 0;
}

第六章 

Winpcap编程

winPcap分为三个层次:网络层,用户层和核心层。
NPF:网络组包过滤。捕获、分析、发送。
NIC:网络适配器。管理网卡。
NDIS:网络驱动接口规范。定义网络适配器和协议驱动之间的标准




抓包必须有的用户级的接口库
packet.dll:底层的api,可以直接访问网路适配器
    运行在用户层
    将应用程序和数据包监听设备驱动程序隔离开来
    应用程序可以不加修改地在不同的WINDOWS系统上运行。

winpcap.dll:强大的高层的捕获程序库,独立于下层的网络适配器和操作系统
    和应用程序编译在一起
    使用底层动态链接库提供的服务,向应用程序提供完善的监听接口。





pcap_findalldevs_ex()函数:获取当前计算机中安装的网卡的列表
成功,则返回0;否则返回-1int pcap_findalldevs_ex(char* source,struct pcap_rmtauth*  auth,pcap_if_t** alldevsp,char* errbuf);

source,指定源的位置。pcap_findalldevs_ex()函数会在指定的源上寻找网络适配器。
    源可以是本地计算机、远程计算机或pcap文件。
    如果源是本地计算机,则该参数使用“rpcap://”;
    如果源是远程计算机,则该参数使用被指定为“rpcap://host:port”的格式;
    如果源是pcap文件,则该参数可以被指定为“file://c:/myfolder/”的格式。
auth:指向pcap_rmtauth结构体的指针。其中保存到远程主机的RPCAP连接所需要的认证信息。
alldevsp:指向pcap_if_t结构体的指针。在调用函数时,函数将为其分配内存空间;当函数返回时,该指针指向获取到的网络设备链表中的第1个元素。
errbuf:指向一个用户分配的缓冲区。其大小为PCAP_ERRBUF_SIZE,该缓冲区中包含调用函数时可能产生的错误信息。
    错误的原因可以是以下几种情况:
        在本地或远程主机上没有安装libpcap或WinPcap。
        用户没有足够的权限获取网络设备列表。
        出现网络问题。
        RPCAP版本协商失败。
        其他问题,比如没有足够的内存等




pcap_rmtauth结构体:保存远程主机上的用户认证信息
struct pcap_rmtauth
{
    int type;
    char *username;
    char *password;
};

type,身份认证的类型。
username,远程主机上用户认证时使用的用户名。
password,远程主机上用户认证时使用的密码。


pcap_if_t结构体:保存获取到的网络设备信息,它等同于pcap_if结构体。
typedef struct pcap_if pcap_if_t; 
pcap_if结构体的定义代码如下: 
struct pcap_if {
    struct pcap_if *next; 
    char *name;     
    char *description;  
    struct pcap_addr *addresses; 
    u_int flags;        
};

next,如果不为NULL,则指向链表中的下一个元素;否则当前元素为链表中的最后一个元素。
name,网络设备的名称。
description,网络设备的描述信息。
addresses,接口上定义的地址列表中的第1个元素。
flags,PCAP_IF_接口标识。目前该标识只可能是PCAP_IF_LOOPBACK,这会设置该接口为回环接口




pcap_freealldevs()函数:用于释放获取到的网络设备链表
pcap_freealldevs(pcap_if_t* alldevsp);

alldevsp:要释放的结构列表




pcap_open()函数:打开与网络适配器绑定的设备,用于捕获和发送数据
pcap_t* pcap_open(
                 const char*    source,
                 int            snaplen,
                 int            flags, 
                 int            read_timeout,
                 struct pcap_rmtauth*      auth,
                 char*           errbuf
                );
source:指定要打开的源设备名称。
    调用pcap_findalldevs_ex()函数返回的适配器可以直接使用pcap_open()函数打开。
snaplen:指定必须保留的数据包长度。
    对于过滤器收到的每个数据包,只有前面的snaplen个字节会被存储到缓冲区中,并且被传送到用户应用程序。例如,snaplen等于100,则每个包中只有前面100个字节会被存储。这样可以减少应用程序间复制数据的量,从而提高捕获数据的效率。
flags:保存几个捕获数据包所需要的标识。
    可以使用该参数来指示网络适配器是否被设置成混杂模式。一般情况下,适配器只接收发给自己的数据包,而其他主机之间通信的数据包将会被丢弃。如果设置为混杂模式,则WinPcap会捕获所有的数据包。
read_timeout:读取超时时间,单位为毫秒。
    在捕获一个数据包后,读操作并不必立即返回,而是等待一段时间以允许捕获更多的数据包。如果平台不支持读取操作,则忽略该参数的值。
auth,指向一个pcap_rmtauth结构体的指针,保存远程计算机上用户所需的认证信息。
    如果不是远程捕获,则该参数被设置为NULL。
errbuf:指向一个用户分配的缓冲区,其大小为PCAP_ERRBUF_SIZE,该缓冲区中包含错误信息。




pcap_loop()函数:用于采集一组数据包
int pcap_loop(
                 pcap_t*          p,
                 int               cnt,
                 pcap_handler    callback,
                 u_char*          user
               ) 
pcap_t:指定一个打开的WinPcap会话,并在该会话中采集数据包。
cnt:要采集的数据包数量。
callback:采集数据包后调用的处理函数。
user:传递给回调函数callback的参数。
    如果成功采集到cnt个数据包,则函数返回0;
    如果出现错误,则返回-1;
    如果用户在未处理任何数据包之前调用pcap_breakloop()函数,则pcap_loop()函数被终止,并返回-2void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data); 
    param,在pcap_loop()函数中指定的参数user。
    header,指向pcap_pkthdr结构体的指针,表示接收到的数据包头。
    pkt_data,接收到的数据包内容。





pcap_compile()函数:将一个高层的布尔过滤表达式编译成一个能够被过滤引擎所解释的低层字节码
发生错误,则返回-1;否则返回0。
int  pcap_compile(
        pcap_t* p,
        struct bpf_program* fp,
        char* str,
        int    optimize,
        bpf_u_int32 netmask);
p:指定一个打开的WinPcap会话,并在该会话中采集数据包。调用pcap_open()函数打开与网络适配器绑定的设备,可以返回WinPcap会话句柄pcap_t。
fp:指向bpf_program结构体的指针,在调用pcap_compile()函数时被赋值,可以为pcap_setfilter()传递过滤信息。
str:指定要保留的数据包协议的字符串,例如,设置该参数为"ip and tcp"表示保留IP数据包和TCP数据包。
optimize:用于控制结果代码的优化。
netmask:指定本地网络的子网掩码。



pcap_setfilter()函数:将一个过滤器与内核捕获会话向关联。
发生错误,则返回-1;否则返回0。
int pcap_setfilter(
             pcap_t *p, 
         struct bpf_program *fp)
p:指定一个打开的WinPcap会话,并在该会话中采集数据包。调用pcap_open()函数打开与网络适配器绑定的设备,可以返回WinPcap会话句柄pcap_t。
fp:指向bpf_program结构体的指针,通常取自pcap_compile()函数调用,可以为pcap_setfilter()传递过滤信息。

winpcap运行流程:
调用pcap_findalldevs_ex()函数获取并打印本机的网络设备列表。
要求用户选择用于捕获数据包的网络设备。
使用for语句跳转到选中的网络设备,以便在后面的程序中打开该设备,并在该设备上捕获数据。
调用pcap_open()函数打开选择的网络设备。
调用pcap_freealldevs()释放网络设备列表。
调用pcap_datalink()函数检查数据链路层,这里只考虑以太网的情况,因此过滤掉不属于以太网的数据包。
调用pcap_compile()函数编译过滤器,只处理IP/UDP数据包。
调用pcap_setfilter()函数设置过滤器。
调用pcap_loop()函数开始捕获数据。当数据包到达时,WinPcap会自动调用回调packet_handler()对数据包进行处理。

 

#include<stdio.h>
#include<string.h>
int main(int argc,char **argv)
{
    int x;

    const char* addr={"192.168.194.195"};
    unsigned long net;

    struct sockaddr_in adr_inet;
    memset(&adr_inet,0,sizeof(adr_inet));
    adr_inet.sin_family = AF_INET;
    adr_inet.sin_port = htons(2000);
    if(!inet_aton(addr,&adr_inet.sin_addr))
    {
        puts("bad address");
    }

    net = inet_netof(adr_inet.sin_addr);
    printf("%l4s:",inet_ntoa(adr_inet.sin_addr));
    memset(&adr_inet,0,sizeof(adr_inet));
    adr_inet.sin_family=AF_INET;
    adr_inet.sin_port=htons(9000);
    adr_inet.sin_addr=inet_makeaddr(net,0);
    printf("%14s:",inet_ntoa(adr_inet.sin_addr));

    return 0;
}

 

推荐阅读