《unix网络编程》笔记

inet_pton和inetntop函数。字母p和n代表presentation和numeric。地址的表达presentation格式通常是ASCIL串,数值(numeric)格式则是存在于套接字地址结构中的二进制值。

inet_pton和inet_ntop函数是比较新的函数,它们能够处理ipv4和ipv6的地址转换。

1. inet_pton

int inet_pton(int af, const char *src, void *dst);

将src所指的网络地址字符串(如"192.168.0.1")转换成网络使用的二进制数(unsigned long),存放在dst所指的in_addr结构中。使用基本与inet_aton一致,不同的是多了一个参数af(地址族:AF_INET或AF_INET6,分别对应ipv4和ipv6,对应的地址结构为sockaddr_in和sockaddr_in6)。

使用:

sockaddr_in server_addr;
inet_pton(AF_INET, "192.168.0.1", (void *)&server_addr.sin_addr);

2. inet_ntop

const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);

将src所指的网络二进制数转换成网络地址字符串(如"192.168.0.1"),存放在dst所指字符串和返回值中。相比inet_pton多了个参数cnt,定义缓存区dst的大小,防止溢出。如果缓存区太小无法存储地址的值,则返回一个空指针,并将errno置为ENOSPC。

使用:

sockaddr_in server_addr;
char IPdotdec[20]; //存放点分十进制IP地址
inet_pton(AF_INET, "192.168.0.1", (void *)&server_addr.sin_addr);
printf(inet_ntop(AF_INET, (void *)&server_addr.sin_addr, IPdotdec, 16));  // 输出"192.168.0.1"
printf("%s", IPdotdec);  // 输出"192.168.0.1"

 readn、writen和readline函数

字节流套接字上的read和write函数所表现的行为不同于通常的文件I/O.字节流套接字上调用read或write输入或输出的字节数可能比请求的数量少,然而这不是出错状态.这个现象的原因在于内核中用于套接字的缓冲区可能已经达到了极限.此时所需的是调用者再次调用read个write函数,以输入或输出剩余的字节. 我们提供的以下三个函数是每当我们读或写一个字节流套接字时要使用的函数.

//从一个描述符读取n个字节
ssize_t readn(int fd, void* vptr, size_t n)
{
 size_t  nleft = n;  //记录还剩下多少字节数没读取
 ssize_t nread;      //记录已经读取的字节数
 char*  ptr = vptr;  //指向要读取数据的指针
 while(nleft > 0)    //还有数据要读取
 {
  if(nread = read(fd,ptr,nleft) < 0)
   if(erron == EINTR)//系统被一个捕获的信号中断
    nread = 0;       //再次读取
   else
    return -1;       //返回
  else if(nread == 0)//没有出错但是也没有读取到数据
   break;            //再次读取
  nleft -= nread;    //计算剩下未读取的字节数
  ptr  += nread;     //移动指针到以读取数据的下一个位置
 }
 return (n-nleft);   //返回读取的字节数
}
/**************************************************************************************************/
//从一个描述符读文本行,一次一个字节
ssize_t readline(int fd, void* vptr, size_t maxlen)//一个字节一个字节地读取
{
 ssize_t  rc;        //每次读取的字符
 ssize_t  n;         //读取的次数也即读取字符串的长度
 char     c;         //
 char* ptr = vptr;   //指向要读取的数据的指针
 for(n = 1;n < maxlen; n++)
 {
  again:
  if((rc = read(fd,&c,1)) == 1)
  {
   *ptr++ = c;       //移动指针
   if(c == ‘\n‘)     //换行符
    break;           //跳出循环
   else if(rc == 0)  //结束
    *ptr = 0;        //字符串以0结尾
   return (n-1);     //返回读取的字节数 末尾的0不算
  }
  else
  {
   if(erron == EINTR)
    goto again;      //重新读取
   return (-1)
  }
 }
 *ptr=0;
 return n;
}
/**************************************************************************************************/
//往一个描述符写n个字节
ssize_t writen(ind fd, const void* vptr, size_t n)
{
 size_t nleft = n;        //还需要写入的字节数
 ssize_t nwritten;        //每次写入的字节数
 const char* ptr = vptr;  //指向要写入的数据的指针
 while(nleft > 0)
 {
  if((nwritten = write(fd,ptr,nleft)) <= 0)
  {
   if(nwritten < 0 && erron == EINTR)
    nwritten = 0;
   else return -1;
  }
   nleft -= nwritten;     //计算还需要写入的字节数
 ptr += nwritten;         //移动数据指针
  }
  return n;
 }

值-结果参数
      一个套接字函数传递一个套接字地址结构时候,该结构总以引用形式来传递,也就是说传递的指向该结构的一个指针,该结构的长度也作为一个参数来传递,不过其传递方式取决于该结构的传递方向:是从进程到内核,还是从内核到进程.如下图所示:

(1)、从进程到内核传递套接字结构函数:bind、connect和sendto.这些函数的一个参数是指向某个套接字地址结构的指针,另一个参数是该结构体的整数大小.例如:

[cpp] view plaincopy

  1. struct sockaddr_in serv; //定义一个套接字地址结构体变量
  2. connect (sockfd, (struct sockaddr *) &serv, sizeof(serv));

(2)、从内核到进程传递套接字地址结构的函数:accept、recvfrom、getsockname和getpeername.这4个函数的其中两个参数指向某个套接字结构体的指针和指向表示该结构体大小的整数变量的指针.例如

[cpp] view plaincopy

  1. struct sockaddr_un cli;                          //定义一个套接字地址结构体变量
  2. socklen_t len = sizeof(cli);                     //该结构体的大小
  3. getpeername(unixfd,(struct sockaddr *)&cli,&len);//len可能会被改变

把套接字地址结构大小这个参数从一个整数改为指向某个整数变量的指针,其原因在于:当函数被调用时,结构大小是一个值,他告诉内核该结构的大小,这样内核在写该结构时不至于越界;当函数返回时,结构大小又是一个结果,它告诉进程内核在该结构中究竟存储了多少信息.

fork和exec函数:

fork最困难的部分是它调用一次却返回两次。在调用进程(称为父进程),它返回一次,返回值是新派生进程(称为子进程)的进程ID,在子进程中它还返回一次,返回0。可通过返回值来判断当前进程是子进程还是父进程。

fork在子进程返回0而不是父进程ID,原因是:子进程只有一个父进程,他总可以调用getppid来得到;而父进程有许多子进程,他没有办法得到各子进程ID。如果父进程想跟踪所有子进程ID,他必须记住fork的返回值。

getsockname与getpeername

是返回套接口关联的本地协议地址和远程协议地址。

int getsockname(int sockfd, struct sockaddr * localaddr, socken_t * addrlen);

int getpeername(int sockfd, struct sockaddr * peeraddr, socken_t * addrlen);

返回0表示成功,返回1表示出错

参数sockfd表示你要获取的套接口的描述字。

localaddr返回本地协议地址描述结构, peeraddr返回远程协议地址描述结构,addrlen分别是上述2个结构的长度。

注意,2个函数的最后一个参数是值-结果参数。

需要这两个函数的理由如下:

  • 在一个没有调用bind的TCP客户上,connect成功返回后,getsockname用于返回由内核赋予该连接的本地IP地址和本地端口号。
  • 在以端口号为0调用bind(告知内核去选择本地临时端口号)后,getsockname用于返回由内核赋予的本地端口号。
  • 在一个以通配IP地址调用bind的TCP服务器上,与某个客户的连接一旦建立(accept成功返回),getsockname就可以用于返回由内核赋予该连接的本地IP地址。在这样的调用中,套接字描述符参数必须是已连接套接字的描述符,而不是监听套接字的描述符。
  • 当一个服务器是由调用过accept的某个进程通过调用exec执行程序时,它能够获取客户身份的唯一途径便是调用getpeername。
  • 例如下面的,inetd调用accept(左上方的方框)返回两个值:已连接套接字描述符connfd,这是函数的返回值;客户的IP地址及端口号,如图中标有“对端地址”的小方框所示(代表一个网际网套接字地址结构)。inetd随后调用fork,派生出inetd的一个子进程。这样父进程的那个套接字地址结构在子进程也可用,那个已连接套接字描述符也是如此。然而当子进程调用exec执行真正的服务器程序(譬如说Telent服务器程序)时,子进程的内存映像被替换成新的Telnet服务器的程序文件(也就是说包含对端地址的那个套接字地址结构就此丢弃),不过那个已连接套接字描述符跨exec继续保持开放。Telnet服务器首先调用的函数之一便getpeername
    ,用于获取客户的IP地址和端口号。
  • 显然,最后一个例子中的Telnet服务器必须在启动之后获取connfd的值。获取该值有两个常用方法:
    • 调用exec的进程可以把这个描述符格式化成一个字符串,再把它作为一个命令行参数传递给新程序。
    • 约定在调用exec之前,总是把某个特定描述符置为所接受的已连接套接字的描述符。

    inetd采用的是第二种方法,它总是把描述符0、1、2置为所接受的已连接套接字的描述符(即将已连接套接字描述符dup到描述符0、1、2,然后close原连接套接字)。

    服务器的代码:

    #include    "unp.h"  
    
    int
    main(int argc, char ** argv)
    {
        int         listenfd,connfd;
        struct      sockaddr_in servaddr;
        pid_t       pid;
        char        temp[20];  
    
        listenfd = Socket(AF_INET, SOCK_STREAM, 0);
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(10010);
        Bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
        Listen(listenfd, LISTENQ);
        for( ; ; )
        {
            struct sockaddr_in local;
            connfd = Accept(listenfd, (SA *)NULL, NULL);
            if((pid = fork()) == 0)
            {
                Close(listenfd);struct sockaddr_in serv, guest;
                char serv_ip[20];
                char guest_ip[20];
                socklen_t serv_len = sizeof(serv);
                socklen_t guest_len = sizeof(guest);
                getsockname(connfd, (struct sockaddr *)&serv, &serv_len);
                getpeername(connfd, (struct sockaddr *)&guest, &guest_len);
                Inet_ntop(AF_INET, &serv.sin_addr, serv_ip, sizeof(serv_ip));
                Inet_ntop(AF_INET, &guest.sin_addr, guest_ip, sizeof(guest_ip));
                printf("host %s:%d guest %s:%dn", serv_ip, ntohs(serv.sin_port), guest_ip, ntohs(guest.sin_port));
                char buf[] = "hello world";
                Write(connfd, buf, strlen(buf));
                Close(connfd);
                exit(0);
            }
            Close(connfd);
        }
    }

    客户端的代码:

    #include "unp.h"
    #define DEST_IP "127.0.0.1"  
    
    int
    main(int argc, char ** argv)
    {
        int         sockfd, n;
        char        buf[100];
        char        serv_ip[20], guest_ip[20];
        struct      sockaddr_in servaddr;  
    
        sockfd = Socket(AF_INET, SOCK_STREAM, 0);
        bzero(&servaddr, sizeof(struct sockaddr_in));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(10010);  
    
        Inet_pton(AF_INET, DEST_IP, &servaddr.sin_addr);
        Connect(sockfd, (SA *)&servaddr, sizeof(servaddr));  
    
        struct sockaddr_in serv, guest;
        socklen_t serv_len = sizeof(serv);
        socklen_t guest_len = sizeof(guest);  
    
        getsockname(sockfd, (SA *)&guest, &guest_len);
        getpeername(sockfd, (SA *)&serv, &serv_len);  
    
        Inet_ntop(AF_INET, &guest.sin_addr, guest_ip, sizeof(guest_ip));
        Inet_ntop(AF_INET, &serv.sin_addr, serv_ip, sizeof(serv_ip));  
    
        printf("host  %s:%d, guest  %s:%dn", serv_ip, ntohs(serv.sin_port), guest_ip, ntohs(guest.sin_port));  
    
        n = Read(sockfd, buf, 100);
        buf[n] = ‘?‘;
        printf("%sn", buf);
        Close(sockfd);
        exit(0);
    }

    TCP

    对于服务器来说,在bind以后就可以调用getsockname来获取本地地址和端口,虽然这没有什么太多的意义。getpeername只有在链接建立以后才调用,否则不能正确获得对方地址和端口,所以他的参数描述字一般是链接描述字而非监听套接口描述字。

    对于客户端来说,在调用socket时候内核还不会分配IP和端口,此时调用getsockname不会获得正确的端口和地址(当然链接没建立更不可能调用getpeername),当然如果调用了bind 以后可以使用getsockname。想要正确的到对方地址(一般客户端不需要这个功能),则必须在链接建立以后,同样链接建立以后,此时客户端地址和端口就已经被指定,此时是调用getsockname的时机。

http://blog.chinaunix.net/zt/1016/unixjian_1016285.shtml

http://blog.csdn.net/yirancpp/article/details/8446879

http://tech.ddvip.com/2013-07/1374769636199661.html

《unix网络编程》笔记,布布扣,bubuko.com

时间: 2024-11-08 19:10:14

《unix网络编程》笔记的相关文章

SQL 笔记 By 华仔

-------------------------------------读书笔记------------------------------- 笔记1-徐 最常用的几种备份方法 笔记2-徐 收缩数据库的大小的方法 笔记3-徐 设置数据库自动增长注意要点 笔记4-徐 模仿灾难发生时还原adventurework数据库 示例 stopat 笔记5-徐 检查日志文件不能被截断的原因 笔记6-徐 检测孤立用户并恢复孤立用户到新的服务器 解决数据库镜像孤立用户问题 笔记7-徐 SQLSERVER日志记录

SQL笔记---多表左联

这是实际场景当中的一个例子,拿出来分析总结思路. -- SQL 查询 --SELECT  orderQuery.Rk_SkuCode ,        orderQuery.SkuName,        SUM(ISNULL(orderQuery.OrderTotal, 0))        - SUM(ISNULL(removeQuery.RemoveTotal, 0))        - SUM(ISNULL(pickQuery.PickTotal, 0))        - SUM(IS

SQL笔记---分页

随用随想,随用随记. 通过实际应用掌握SQL语句. 一. SQL分页 1. 第一种方法:利用ID大于多少进行筛选 SELECT TOP 20        *FROM    dbo.WMS_StockWHERE   ( Rk_SkuCode > ( SELECT MAX(Rk_SkuCode)                         FROM   ( SELECT TOP 40                                            *           

《HeadFirst SQL》笔记

规范化 0 约束 1 原子性 2 第一范式 1NF 3 数据模式 4 依赖 5 联接查询 6 交叉联接(AKA 笛卡尔联接,叉积) 7 内联接 8 子查询 9 外联接 10 自联接 11 集合 12 事务 13 ACID 14 管理事务 15 常用语句 16 注意 17 规范化 约束 NOT NULL UNIQUE PRIMARY KEY DEFAULT FOREIGN KEY:引用父表的某个唯一值引用完整性:插入外键列的值必须已经存在于父表的来源列中 --创建外键 create table i

SQL笔记1:SELECT及SELECT高级应用

T-SQL笔记1:SELECT及SELECT高级应用 本章摘要 1:安装AdventureWorks 2:基本运算符和表达式 3:between 4:like 5:escape 6:TOP 7:GROUP BY 7.1:GROUP BY ALL 7.2:HAVING 8:SELECT字句技术 8.1:使用DISTINCT消除重复值 8.2:返回拼接的结果 8.3使用INTO字句 9:子查询 9.1:子查询类型 9.2:代替表达式的查询 9.3:多层嵌套 10:比较使用 EXISTS 和 IN 的

金典 SQL笔记(6)

page223-索引 --利用SQL 语句创建索引 --CREATE INDEX 索引名称on 表名(字段 ,字段, 字段字段n) --索引名称必须为唯一的,字段 ,字段, 同意一个到多个 --范例为T_person 表中给FName创建索引索引名为 idx_person_name CREATE INDEX idx_person_name ON T_Person (FName) --删除索引 --drop index 表名索引名 DROP INDEX T_person.idx_person_na

Mybatis 项目开发实际常用SQL笔记总结

parameterType 和 resultType parameterType:单个参数用String,多个参数用map resultType:   可以是 Integer.String.Object    <select id="countGroupMasterByUid" parameterType="String" resultType="Integer">      SELECT              COUNT(id)

sql笔记/分页存储过程

[email protected]c#中进行++操作可以是整数或小数,sql中只能对整数进行++操作.char类型 适合存储长度波动较小不回收效率高varchar 类型 适合存储长度波动较大可以回收nchar代表unicode 存储内容包括汉字时候考虑加n SQL语句特点1不区分大小写2没有双引号所有字符串都包含在单引号3没有逻辑相等,逻辑相等和赋值一样都是用=4没有bool值得概念,但是在视图中可以输入true/false5也有关系运算符.6也有逻辑运算符 &&-- and || --o

sql笔记

1. 看下面sql,重点有两个,一个是distinct  ,一个是树形结构查询 select DISTINCT t.unit_code from t_unit_relation t where t.corp_tn='jiaozhougongan' start with t.unit_code='0001' connect by prior t.unit_code = t.unit_upcode 分析: ① distinct:去重复值 ② 树形结构查询,这个博客:http://www.cnblog

HeadFirst SQL 读书摘要

数据库都是用 圆柱形表示的. 数据库中包含表 表中包含行和列 行又叫记录record,  列又叫 字段field 创建数据库 create database mypipe_l; 选择数据库 use mypipe_l; 创建表 create table doughnut( name VARCHAR(10), type VARCHAR(6) ); 查看表 desc doughnut; 删除表 drop table doughnut; 插入数据 insert into doughnut (name,