UNIX网络编程卷1:套接字联网-第4章:基本TCP套接字编程1

还是那句话,我们要学会使用man查看

1.socket函数

根据指定的协议族、套接字类型和协议来分配一个套接口的描述符及其所用的资源,返回分配的套接字描述符

 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>
 int socket(int domain,int type,int protocol);

此处需要重点理解socket每个参数的含义:

  • domain:指定协议族,我们可以通过man来查看不同类unix操作系统提供的常量值。在我的ubuntu14.04中

我们常用的AF_UNIX(Unix域协议),AF_INET(ipv4协议),AF_INET6(ipv6协议)

  • type:制定套接字类型
    • SOCK_STREAM :字节流套接字
    • SOCK_DGRAM:数据报套接字
    • SOCK_SEQPACKET:有序分组套接字
    • SOCK_RAW:原始套接字
  • protocol:使用的传输层协议
    • IPPROTO_TCP: tcp传输协议
    • IPPROTO_UDP:udp传输协议
    • IPPROTO_SCTP:SCTP传输协议

*****************socket在网络编程中的调用位置******************************************



2.connect函数

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
  • sockfd:传入由socket函数返回的套接字描述符。
  • addr:指向套接字地址结构的指针
  • 套接字地址结构的大小

知识点总结:

  • 如果是tcp套接字,connect函数激发tcp的三路握手过程
  • 查看connect函数返回结果。若返回成功,则tcp连接建立成功,客户端tcp状态从SYN_SENT -> CONNECTED
  • connect出错返回的几种情况
    • 情况1:若TCP客户没有收到SYN分节的响应,则返回ETIMEDOUT错误。(超时错误)
    • 情况2:若对客户的SYN的响应是RST(复位),表明该服务器主机在我们指定的端口上没有进程在等待与之连接(比如:服务器进程也许没在运行)。这是硬件错误,客户端接收到RST就马上返回ECONNREFUSED。
    • 情况3:若客户发出的SYN在中间的某个路由器上引发了一个“destination unreachable"(目的地不可达)ICMP错误,则认为是一个软错误。客户主机内核保存该信息,并按重传机制间隔继续发送SYN。若在某个规定的时间后仍未收到响应,则把保存的消息(即ICMP)作为EHOSTUNREACH或ENETUNREACH错误返回给进程。以下情况也是可能的:一是按照本地系统的路由表,根本没有到达远程系统的路径。二是connect调用根本不等待直接返回。

    RST解析:

  • RST是TCP在发生错误时发送的一种TCP分节(RST本身也是一个分节)
  • 产生RST的三个条件:(记清楚了)目的地为某端口的SYN到达,然而该端口上没有正在监听的服务器;TCP想取消一个已有连接;TCP接收到一个根本不存在的连接上的分节

调试验证(纸上得来终觉浅,绝知此事要躬行)

运行书中时间服务程序 intro目录下

sudo ./daytimetcpsrv
另开一个终端,使用netstat命令查看服务是否成功开启,结果如下

sunxiaowu@sunxiaowu:~$ sudo netstat -anp | grep 'daytime'
tcp        0      0 0.0.0.0:13              0.0.0.0:*               LISTEN      2714/daytimetcpsrv
sunxiaowu@sunxiaowu:~$
运行客户端时间获取程序
sunxiaowu@sunxiaowu:~/Downloads/unpv13e/intro$ ./daytimetcpcli 127.0.0.1
Fri Jul  7 10:53:55 2017
sunxiaowu@sunxiaowu:~/Downloads/unpv13e/intro$
成功获得服务器时间

现在我们来模拟connect出错情况1

我们尝试指定主机不存在的一个IP地址,这样当客户主机发送请求(要求那个不存在的主机响应以其硬件地址)时,它将永远收不到ARP响应(这个ARP响应是什么,以后详解,可以参看tcp/ip详解),收不到SYN分节的响应。

sunxiaowu@sunxiaowu:~/Downloads/unpv13e/intro$ ./daytimetcpcli 192.122.2.2
connect error: Connection timed out
sunxiaowu@sunxiaowu:~/Downloads/unpv13e/intro$ 
这个等待的具体时间可能有点长(中间会涉及tcp的重传机制),注意看报错的函数,正是connect

现在我们来模拟connect出错情况2

典型情况是可以通过ip找到主机,也就是主机会有回应,但客户端请求的服务端口并没有进程等待与之连接。我们可以把时间服务程序关掉,直接运行客户端时间获取程序来模拟此种错误。

sunxiaowu@sunxiaowu:~/Downloads/unpv13e/intro$ sudo netstat -anp |grep 'daytime' 
[sudo] password for sunxiaowu: 
tcp        0      0 0.0.0.0:13              0.0.0.0:*               LISTEN      2714/daytimetcpsrv
sunxiaowu@sunxiaowu:~/Downloads/unpv13e/intro$ sudo kill 2714
sunxiaowu@sunxiaowu:~/Downloads/unpv13e/intro$ sudo netstat -anp |grep 'daytime'sunxiaowu@sunxiaowu:~/Downloads/unpv13e/intro$ ./daytimetcpcli 127.0.0.1
connect error: Connection refused
sunxiaowu@sunxiaowu:~/Downloads/unpv13e/intro$ 
注意看报错,主机会响应一个RST分节,导致connect报错,报错信息connection refused

现在我们来模拟connect出错情况3

我们指定一个不可到达的ip地址。

sunxiaowu@sunxiaowu:~/Downloads/unpv13e/intro$ ./daytimetcpcli 192.168.1.33
connect error: No route to host
sunxiaowu@sunxiaowu:~/Downloads/unpv13e/intro$ 

connect总结:

结合tcp状态图(可以查看我的另一篇博文:TCP状态转换详解),

  • 主动连接方(一般就是客户端)应用进程调用connect时,会发送SYN分节给服务端,状态CLOSED -> SYN_SENT,接收到对方的ACK和SYN分节后,connect成功返回,状态

SYN_SENT -> CONNECTED

  • 若connect失败,则该套接字不能再使用,必须关闭,我们不能对这个套接字再次调用connect。(以前挂过彩,恍如昨日)


3.bind函数

把一个本地协议地址赋予一个套接字。

  #include <sys/types.h>          /* See NOTES */
  #include <sys/socket.h>
  int bind(int sockfd,socklen_t addrlen);

此函数直接查看man手册学习吧

注意一点,bind返回的而一个常见错误是EADDRINUSE(地址已使用)

4.listen函数

  #include <sys/types.h>          /* See NOTES */
  #include <sys/socket.h>
  int listen(int sockfd,int backlog);
  • 调用listen函数导致套接字从CLOSED -> LISTEN
  • listen把未连接的套接字(默认主动)转换成一个被动套接字(也就是说,它指示内核应接收指向该套接字的请求)
  • ****第二个参数规定内核应该为相应套接字排队的最大连接数

重点:内核会为任何一个给定的监听套接字维护两个队列:


这个在面试中也是被人家常问的(内核具体怎么维护这两个队列,以后在内核解析中慢慢讲述)

当来自客户的SYN到达时,TCP在未完成连接队列中创建一个新项,然后响应以三路握手的第二个分节。这一项一直保留在未完成队列中,直到三路握手的第三个分节到达或者该项超时为止。若三路握手正常完成,该项就从未完成连接队列转移到已完成连接队列的队尾。当进程调用accept,已完成连接队列中的对头项将返回给进程。如果队列为空,则进程将被投入睡眠,直到tcp在该队列中放入一项才唤醒它。

如何获取系统支持的最大LISTENQ?

NAME
       getenv,secure_getenv - get an environment variable

SYNOPSIS
       #include <stdlib.h>
       char *getenv(const char *name);
       char *secure_getenv(const char *name);

重点:

  • 当一个客户SYN到达时,若这些队列是满的,TCP就忽略该TCP分节,也就是不发送RST。客户TCP将重发SYN,期望不久能在队列中找到可用空间。

  • 在三路握手完成之后,但在服务器调用accept之前到达的数据应由服务器tcp排队,最大数据量为相应已连接套接字的接收缓冲区大小。

5.accept函数

#include<sys/socket.h>

int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);

返回值:若成功则为非负描述符,若出错则为-1

  • 如果accept成功,那么其返回值为由内核自动生成的一个全新描述符,代表与所返回客户的TCP连接。
  • accept中的第一个参数为监听套接字描述符,返回值为已连接套接字描述符

6.fork函数

 #include <unistd.h>
   pid_t fork(void);
返回:在子进程中为0,父进程中为子进程ID,若出错则为-1
  • fork调用一次,返回2次
  • 调用进程返回一次,返回值为新派生进程的pid,子进程返回一次,返回值为0
  • 父进程调用fork之前打开的所有描述符在fork返回之后由子进程分享。

fork两种典型用法:

  • 一个进程创建一个自身的副本,这样每个副本都可以在另一个副本执行其他任务的同时处理各自的操作
  • 一个进程想要执行另一个程序。fork,然后其中一个副本调用exec族函数(把当前进程映像替换成新的程序文件,从main开始执行,进程id不变。

7.exec族函数


8.close函数

  #include <unistd.h>
  int close(int fd);
用来关闭套接字,并终止tcp连接

重点:close一个tcp套接字的默认行为是把该套接字标记为已关闭(导致相应描述符的引用计数值减1),然后立即返回到调用进程。该套接字描述符不能再由调用进程使用(不能再作为read或write的第一个参数)

注意,此时tcp将尝试发送已排队等待的到对端的任何数据(发送队列),发送完毕后发生的是正常的tcp连接终止序列(计数为0才会引发四分组连接终止)。


9.shutdown函数

#include<sys/socket.h>
int shutdown(int sockfd,int howto);
若成功则为0,出错返回-1

终止网络的通常方法:调用close函数。不过close有两个限制,却可以使用shutdown避免

  • close把描述符的引用计数减1,仅在该计数变为0时才关闭套接字。而使用shutdown可以直接触发tcp的正常连接终止序列。
  • close终止读和写两个方向数据的传送(close某个套接字后,此套接字描述符不能再做为read或write的第一个参数),而shutdown行为依赖于howto
  • howto参数详解:

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


用的openwrt路由器,家里宽带申请了动态公网ip,为了方便把2280端口映射到公网,发现经常被暴力破解,自己写了个临时封禁ip功能的脚本,实现5分钟内同一个ip登录密码错误10次就封禁这个ip5分钟,并且进行邮件通知使用步骤openwrt为19.07.03版本,其他版本没有测试过安装bashmsmtpopkg
#!/bin/bashcommand1&command2&wait从Shell脚本并行运行多个程序–杨河老李(kviccn.github.io)
1.先查出MAMP下面集成的PHP版本cd/Applications/MAMP/bin/phpls-ls 2.编辑修改.bash_profile文件(没有.bash_profile文件的情况下回自动创建)sudovim~/.bash_profile在文件的最后输入以下信息,然后保存退出exportPATH="/Applications/MAMP/bin/php/php7.2.20/b
1、先输入locale-a,查看一下现在已安装的语言2、若不存在如zh_CN之类的语言包,进行中文语言包装:apt-getinstalllanguage-pack-zh-hans3、安装好后我们可以进行临时修改:然后添加中文支持: locale-genzh_CN.UTF-8临时修改> export LC_ALL='zh_CN.utf8'> locale永久
BashPerlTclsyntaxdiff1.进制数表示Languagebinaryoctalhexadecimalbash2#[0~1]0[0~7]0x[0~f]or0X[0~f]perl0b[0~1]0[0~7]0x[0~f]tcl0b[0~1]0o[0~7]0x[0~f]bashdifferentbaserepresntationreference2.StringlengthLanguageStr
正常安装了k8s后,使用kubect工具后接的命令不能直接tab补全命令补全方法:yum-yinstallbash-completionsource/usr/share/bash-completion/bash_completionsource<(kubectlcompletionbash)echo"source<(kubectlcompletionbash)">>~/.bashrc 
参考这里启动jar包shell脚本修改过来的#!/bin/bash#默认应用名称defaultAppName='./gadmin'appName=''if[[$1&&$1!=0]]thenappName=$1elseappName=$defaultAppNamefiecho">>>>>>本次重启的应用:$appName<
#一个数字的行#!/bin/bashwhilereadlinedon=`echo$line|sed's/[^0-9]//g'|wc-L`if[$n-eq1]thenecho$linefidone<1.txt#日志切割归档#!/bin/bashcd/data/logslog=1.logmv_log(){[-f$1]&&mv$1$2
#文件增加内容#!/bin/bashn=0cat1.txt|whilereadlinedon=[$n+1]if[$n-eq5]thenecho$lineecho-e"#Thisisatestfile.\n#Testinsertlineintothisfile."elseecho$linefidone#备份/etc目录#
# su - oraclesu: /usr/bin/ksh: No such file or directory根据报错信息:显示无法找到文件 /usr/bin/ksh果然没有该文件,但是发现存在文件/bin/ksh,于是创建了一个软连接,可以规避问题,可以成功切换到用户下,但无法执行系统自带命令。$. .bash_profile-ksh: .: .b
history显示历史指令记录内容,下达历史纪录中的指令主要的使用方法如果你想禁用history,可以将HISTSIZE设置为0:#exportHISTSIZE=0使用HISTIGNORE忽略历史中的特定命令下面的例子,将忽略pwd、ls、ls-ltr等命令:#exportHISTIGNORE=”pwd:ls:ls-ltr:”使用HIS
一.命令历史  1.history环境变量:    HISTSIZE:输出的命令历史条数,如history的记录数    HISTFILESIZE:~/.bash_history保存的命令历史记录数    HISTFILLE:历史记录的文件路径    HISTCONTROL:     ignorespace:忽略以空格开头的命令
之前在网上看到很多师傅们总结的linux反弹shell的一些方法,为了更熟练的去运用这些技术,于是自己花精力查了很多资料去理解这些命令的含义,将研究的成果记录在这里,所谓的反弹shell,指的是我们在自己的机器上开启监听,然后在被攻击者的机器上发送连接请求去连接我们的机器,将被攻击者的she
BashOne-LinersExplained,PartI:Workingwithfileshttps://catonmat.net/bash-one-liners-explained-part-oneBashOne-LinersExplained,PartII:Workingwithstringshttps://catonmat.net/bash-one-liners-explained-part-twoBashOne-LinersExplained,PartII
Shell中变量的作用域:在当前Shell会话中使用,全局变量。在函数内部使用,局部变量。可以在其他Shell会话中使用,环境变量。局部变量:默认情况下函数内的变量也是全局变量#!/bin/bashfunctionfunc(){a=99}funcecho$a输出>>99为了让全局变量变成局部变量
1、多命令顺序执行;  命令1;命令2  多个命令顺序执行,命令之间没有任何逻辑联系&&  命令1&&命令2  逻辑与,当命令1正确执行,才会执行命令2||  命令1||命令2  逻辑或,当命令1执行不正确,才会执行命令2例如:ls;date;cd/home/lsx;pwd;who ddif=输入文件of=输
原博文使用Linux或者unix系统的同学可能都对#!这个符号并不陌生,但是你真的了解它吗?首先,这个符号(#!)的名称,叫做"Shebang"或者"Sha-bang"。Linux执行文件时发现这个格式,会把!后的内容提取出来拼接在脚本文件或路径之前,当作实际执行的命令。 Shebang这个符号通常在Unix系统的脚本
1、历史命令history[选项][历史命令保存文件]选项:-c:  清空历史命令-w:  把缓存中的历史命令写入历史命令保存文件 ~/.bash_historyvim/etc/profile中的Histsize可改存储历史命令数量历史命令的调用使用上、下箭头调用以前的历史命令使用“!n”重复执行第n条历史
目录1.Shell脚本规范2.Shell脚本执行3.Shell脚本变量3.1环境变量3.1.1自定义环境变量3.1.2显示与取消环境变量3.1.3环境变量初始化与对应文件的生效顺序3.2普通变量3.2.1定义本地变量3.2.2shell调用变量3.2.3grep调用变量3.2.4awk调用变量3.3
   http://www.voidcn.com/blog/wszzdanm/article/p-6145895.html命令功能:显示登录用户的信息命令格式:常用选项:举例:w显示已经登录的用户及正在进行的操作[root@localhost~]#w 11:22:01up4days,21:22, 3users, loadaverage:0.00,0.00,0.00USER