Linux网络编程(1)

1、协议的概念:     

1)、什么是协议
    从应用的角度出发,协议可理解为“规则”,是数据传输和数据的解释的规则。
    假设,A、B双方欲传输文件。规定:
    第一次,传输文件名,接收方接收到文件名,应答OK给传输方;
    第二次,发送文件的尺寸,接收方接收到该数据再次应答一个OK;
    第三次,传输文件内容。同样,接收方接收数据完成后应答OK表示文件内容接收成功。
    由此,无论A、B之间传递何种文件,都是通过三次数据传输来完成。A、B之间形成了一个最简单的数据传输规则。双方都按此规则发送、接收数据。A、B之间达成的这个相互遵守的规则即为协议。
    这种仅在A、B之间被遵守的协议称之为原始协议。当此协议被更多的人采用,不断的增加、改进、维护、完善。最终形成一个稳定的、完整的文件传输协议,被广泛应用于各种文件传输过程中。该协议就成为一个标准协议。最早的ftp协议就是由此衍生而来。

2)、TCP协议注重数据的传输。http协议着重于数据的解释。

3)、典型协议
    传输层 常见协议有TCP/UDP协议。
    应用层 常见的协议有HTTP协议,FTP协议。
    网络层 常见协议有IP协议、ICMP协议、IGMP协议。
    网络接口层 常见协议有ARP协议、RARP协议。
    TCP传输控制协议(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
    UDP用户数据报协议(User Datagram Protocol)是OSI参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
    HTTP超文本传输协议(Hyper Text Transfer Protocol)是互联网上应用最为广泛的一种网络协议。
    FTP文件传输协议(File Transfer Protocol)
    IP协议是因特网互联协议(Internet Protocol)
    ICMP协议是Internet控制报文协议(Internet Control Message Protocol)它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。
    IGMP协议是 Internet 组管理协议(Internet Group Management Protocol),是因特网协议家族中的一个组播协议。该协议运行在主机和组播路由器之间。
    ARP协议是正向地址解析协议(Address Resolution Protocol),通过已知的IP,寻找对应主机的MA C地址。
    RARP是反向地址转换协议,通过MAC地址确定IP地址。
    
4)、网络应用程序设计模式
    C/S模式
        传统的网络应用设计模式,客户机(client)/服务器(server)模式。需要在通讯两端各自部署客户机和服务器来完成数据通信。
    B/S模式.
        浏览器()/服务器(server)模式。只需在一端部署服务器,而另外一端使用每台PC都默认配置的浏览器即可完成数据的传输。
        ①优缺点. 
        对于C/S模式来说,其优点明显。客户端位于目标主机上可以保证性能,将数据缓存至客户端本地,从而提高数据传输效率。且,一般来说客户端和服务器程序由一个开发团队创作,所以他们之间所采用的协议相对灵活。可以在标准协议的基础上根据需求裁剪及定制。例如,腾讯公司所采用的通信协议,即为ftp协议的修改剪裁版。
        因此,传统的网络应用程序及较大型的网络应用程序都首选C/S模式进行开发。如,知名的网络游戏魔兽世界。3D画面,数据量庞大,使用C/S模式可以提前在本地进行大量数据的缓存处理,从而提高观感。
        C/S模式的缺点也较突出。由于客户端和服务器都需要有一个开发团队来完成开发。工作量将成倍提升,开发周期较长。另外,从用户角度出发,需要将客户端安插至用户主机上,对用户主机的安全性构成威胁。这也是很多用户不愿使用C/S模式应用程序的重要原因。
     
        ②B/S模式相比C/S模式而言,由于它没有独立的客户端,使用标准浏览器作为客户端,其工作开发量较小。只需开发服务器端即可。另外由于其采用浏览器显示数据,因此移植性非常好,不受平台限制。如早期的偷菜游戏,在各个平台上都可以完美运行。
        B/S模式的缺点也较明显。由于使用第三方浏览器,因此网络应用支持受限。另外,没有客户端放到对方主机上,缓存数据不尽如人意,从而传输数据量受到限制。应用的观感大打折扣。第三,必须与浏览器一样,采用标准http协议进行通信,协议选择不灵活。
        因此在开发过程中,模式的选择由上述各自的特点决定。根据实际需求选择应用程序设计模式。

        ③使用场景:数据量比较大,需要先缓存,用cs,稳定性高
        数据量小,数据访问工作量不大选用bs

2、分层模型

    1)、OSI七层模型

  1. 物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后再转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特。
  2. 数据链路层:定义了如何让格式化数据以帧为单位进行传输,以及如何让控制对物理介质的访问。这一层通常还提供错误检测和纠正,以确保数据的可靠传输。如:串口通信中使用到的115200、8、N、1
  3. 网络层:在位于不同地理位置的网络中的两个主机系统之间提供连接和路径选择。Internet的发展使得从世界各站点访问信息的用户数大大增加,而网络层正是管理这种连接的层。
  4. 传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段。
  5. 会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名)。
  6. 表示层:可确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。例如,PC程序与另一台计算机进行通信,其中一台计算机使用扩展二一十进制交换码(EBCDIC),而另一台则使用美国信息交换标准码(ASCII)来表示相同的字符。如有必要,表示层会通过使用一种通格式来实现多种数据格式之间的转换。
  7. 应用层:是最靠近用户的OSI层。这一层为用户的应用程序(例如电子邮件、文件传输和终端仿真)提供网络服务。

    2)、TCP/IP四层模型

        TCP/IP网络协议栈分为应用层(Application)、传输层(Transport)、网络层(Network)和链路层(Link)四层。如下图所示:

一般在应用开发过程中,讨论最多的是TCP/IP模型

3、数据包的封装:

       数据在传输之前,必须要进行封装,将数据封装上外面的四层,链路层也可以叫做以太网帧格式

传输层及其以下的机制由内核提供,应用层由用户进程提供(后面将介绍如何使用socket API编写应用程序),应用程序对通讯数据的含义进行解释,而传输层及其以下处理通讯的细节,将数据从一台计算机通过一定的路径发送到另一台计算机。应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部(header),称为封装(Encapsulation),如下图所示:

不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报(datagram),在链路层叫做帧(frame)。数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,最后将应用层数据交给应用程序处理。

4、通信过程

数据包封装好之后,将数据包放入网络中,经过路由器寻路,一步一步查找到我们要找的那个地址。

          1、以太网帧格式中会存放目的地址和源地址,通过目的地址和源地址来查找。

          2、通过ARP数据报表,来返回下一个路由器的MAC地址,如上图所示。

          3、最终的IP地址 存放在网络层的ip中,一层一层的寻找最后到最终目的;

          路由器寻路一般思想:
     每一个路由器都有自己的路由表,记录自己连接的路由器都有哪些
     发送的数据封装里面会有一个ip地址记录自己要去的地方,然后选择那个路由器和自己最接近
     寻路:寻找下一个路由节点,不单单依照ip还会依赖以太网帧
     对于TCP来说,寻路只有一次,这条路建立好了以后,所有数据都按照这个路走
     对于UDP来说不会有固定的,每次都会寻路

每到一个路由器,都会重新解压,在打包,再继续传

 

两台计算机通过TCP/IP协议通讯的过程如下所示:

上图对应两台计算机在同一网段中的情况,如果两台计算机在不同的网段中,那么数据从一台计算机到另一台计算机传输过程中要经过一个或多个路由器,如下图所示:

链路层有以太网、令牌环网等标准,链路层负责网卡设备的驱动、帧同步(即从网线上检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就自动重发)、数据差错校验等工作。交换机是工作在链路层的网络设备,可以在不同的链路层网络之间转发数据帧(比如十兆以太网和百兆以太网之间、以太网和令牌环网之间),由于不同链路层的帧格式不同,交换机要将进来的数据包拆掉链路层首部重新封装之后再转发。

网络层的IP协议是构成Internet的基础。Internet上的主机通过IP地址来标识,Inter-net上有大量路由器负责根据IP地址选择合适的路径转发数据包,数据包从Internet上的源主机到目的主机往往要经过十多个路由器。路由器是工作在第三层的网络设备,同时兼有交换机的功能,可以在不同的链路层接口之间转发数据包,因此路由器需要将进来的数据包拆掉网络层和链路层两层首部并重新封装。IP协议不保证传输的可靠性,数据包在传输过程中可能丢失,可靠性可以在上层协议或应用程序中提供支持。

网络层负责点到点(ptop,point-to-point)的传输(这里的“点”指主机或路由器),而传输层负责端到端(etoe,end-to-end)的传输(这里的“端”指源主机和目的主机)。传输层可选择TCP或UDP协议。

TCP是一种面向连接的、可靠的协议,有点像打电话,双方拿起电话互通身份之后就建立了连接,然后说话就行了,这边说的话那边保证听得到,并且是按说话的顺序听到的,说完话挂机断开连接。也就是说TCP传输的双方需要首先建立连接,之后由TCP协议保证数据收发的可靠性,丢失的数据包自动重发,上层应用程序收到的总是可靠的数据流,通讯之后关闭连接。

UDP是无连接的传输协议,不保证可靠性,有点像寄信,信写好放到邮筒里,既不能保证信件在邮递过程中不会丢失,也不能保证信件寄送顺序。使用UDP协议的应用程序需要自己完成丢包重发、消息排序等工作。

 

5、以太网帧格式

 

在整个传输封装里,以太网帧是最外面的链路层,最接近硬件的部分
    格式:6字节的目的地址,6字节的源地址  2字节的类型  还有4字节的校验CRC(奇偶校验等)
    目的地址和源地址对应电脑里的硬件地址也叫MAC地址,网卡编号,这个编号在网络通信中是唯一的 
 
    我怎么样才能知道我要发送的MAC地址 需要借助 ARP请求
    2字节的类型传值传0800的时候是普通的以太网数据包,后面跟的是数据
    如果穿的是0806那么传的是ARP请求/应答  用来请求下一个要经过的路由器地址
    如果穿的是8035后面跟的是RARP请求/应答

6、ARP数据报

前六个字节填充为00:00:00 再六个字节源字节,2字节帧类型(0806),中间八字节不管,发送端MAC,发送端IP,接收端MAC(暂时不知道),接收端IP(已知)

传送到下一个路由器后,这个数据封装的数据包就会发生改变,目的mac变成前一个路由的MAC来时候的源ip,源MAC变成自己的,发送端MAC和发送管ip都是自己的,接收端变成上一个发送来的。

在把当前的发送端MAC取出就可以了
一个路由称为一跳
arp数据报目的:获取下一跳IP地址

数据报寻路小结
最终想要取得IP地址封装在网络层
    每到一个路由器,不仅解开以太网帧,还要解析网络层,获得最终目的IP后,根据自己的路由表判断下一次往哪发,也就选择好了下一跳的IP地址
拥塞住网络环境的话,经过多少跳到达目的端,有一个上限,TTL 以跳为单位,到达TTL后,还没有到达就会丢弃

7、IP段格式

首部长度是前20个字节
生存时间就是能经过多少跳(TTL)
32位源ip地址和32位目的地址 

ip地址是有上限的,正常看到的192.168.34.54是字符串要转为 unisgned int 传递 所以有上限

8、TCP_UDP简介
    常用在传输层的 TCP和UDP常用 用于指定进程(端口号),目的就是传到ip后的某一个进程

    

9、NAT映射:

     192.168是局域网ip  在公网中不可见
    网卡出来的ip先发到交换机上,交换机在进行ip地址的转发,发给路由器
    路由器总除了路由表,还有一个NAT映射表,记录的是当前链接到这个终端的局域IP地址和公有IP的关系
      
    个人向服务器也用公网 也是nat映射 比如个人访问新浪
    但是比如百度向新浪就不用NAT了

10、打洞机制
     通过一个公有IP,当发送和接收两方私有ip都经过这个公有ip时。可以通过这个公有的IP(服务器)打洞,直接连接,提高效率

11、套接字(socket)概念  
        Socket本身有“插座”的意思,在Linux环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。    

提到socket时,一定有两端,发端和收端
        ip地址在网络环境中唯一标示一台主机
        端口号在主机中唯一标示一个集成
        ip地址加端口号在网络环境中唯一标示一个进程(socket)
        所以想使用socket必须由连个  ip和端口号 
        
        套接字是Linux中一种文件类型,伪文件
        也可以用文件描述符来操作socket,一个fd对应两个缓冲区,是全双工的,同时既可以读入也可以输出

1、socket一定成对出现 
2、绑定IP+port
3、一个文件描述符对应两个缓冲区,

在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。

12、网络字节序转化

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节 
    大小端:大端: 低地址放在高位  小段:低对低 高对高

计算机是小端存储,网络断是大端存储,传递时读不到正确数据,所以要进行网络字节序和主机字节序的转换。
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h表示host,n表示network,l表示32位长整数,s表示16位短整数。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

 

ip地址转换函数:
    int inet_pton(int af, const char *src, void *dst);  本地Ip变成网络字节序
    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);网络字节序变成本地ip

inet_pton  af指定版本指定AF_INET 第二个字参数 点分十进制字符串   第三个参数:传出参数,网络字节序ip地址
  af指定版本指定AF_INET        第二个参数网络字节序,第三个参数 字符串指针位置,第四个 字符串大小

13、

sockaddr数据结构

    定义要定义成socketaddr_in类型,使用时要强转成 socketaddr的 有bind accept connect 函数

struct sockaddr_in {

__kernel_sa_family_t sin_family; /* Address family */   地址结构类型

__be16 sin_port;   /* Port number */ 端口号

struct in_addr sin_addr; /* Internet address */ IP地址

/* Pad to size of `struct sockaddr'. */

unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -

sizeof(unsigned short int) - sizeof(struct in_addr)];

};

struct in_addr { /* Internet address. */

__be32 s_addr;

};
    addr.sin_family = AF_INET/AFINET6
    addr.sin_port = htons/ntonl;
    addr.sin_addr.s_addr = htonl; ntosl;|inet_pton inet_ntop

14、网络套接字函数

socket模型创建流程图

socket函数

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

domain:

AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址

AF_INET6 与上面类似,不过是来用IPv6的地址

AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用

type:采用什么通信协议

SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。

SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。

SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。

SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)

SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序

protocol:

传0 表示使用默认协议。

返回值:

成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno

socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1。对于IPv4,domain参数指定为AF_INET。对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。如果是UDP协议,则type参数指定为SOCK_DGRAM,表示面向数据报的传输协议。protocol参数的介绍从略,指定为0即可。

bind函数

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:

socket文件描述符

addr:

构造出IP地址加端口号

addrlen:

sizeof(addr)长度

返回值:

成功返回0,失败返回-1, 设置errno

服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind绑定一个固定的网络地址和端口号。

bind()的作用是将参数sockfd和addr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。前面讲过,struct sockaddr *是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。如:

struct sockaddr_in servaddr;

bzero(&servaddr, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

servaddr.sin_port = htons(6666);

首先将整个结构体清零,然后设置地址类型为AF_INET,网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址,端口号为6666。

listen函数

listen监听同时允许多少个客户端与我建立连接  指定监听上限数

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int listen(int sockfd, int backlog);

sockfd:

socket文件描述符

backlog:

排队建立3次握手队列和刚刚建立3次握手队列的链接数和

查看系统默认backlog

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

典型的服务器程序可以同时服务于多个客户端,当有客户端发起连接时,服务器调用的accept()返回并接受这个连接,如果有大量的客户端发起连接而服务器来不及处理,尚未accept的客户端就处于连接等待状态,listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接待状态,如果接收到更多的连接请求就忽略。listen()成功返回0,失败返回-1。

accept函数:作用 接收一个连接请求

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

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

sockdf:

socket文件描述符

addr:

传出参数,返回链接客户端地址信息,含IP地址和端口号

addrlen:

传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小

返回值:

成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno

三方握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。addr是一个传出参数,accept()返回时传出客户端的地址和端口号。addrlen参数是一个传入传出参数(value-result argument),传入的是调用者提供的缓冲区addr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。如果给addr参数传NULL,表示不关心客户端的地址。

我们的服务器程序结构是这样的:

while (1) {

cliaddr_len = sizeof(cliaddr);

connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);

n = read(connfd, buf, MAXLINE);

......

close(connfd);

}

整个是一个while死循环,每次循环处理一个客户端连接。由于cliaddr_len是传入传出参数,每次调用accept()之前应该重新赋初值。accept()的参数listenfd是先前的监听文件描述符,而accept()的返回值是另外一个文件描述符connfd,之后与客户端之间就通过这个connfd通讯,最后关闭connfd断开连接,而不关闭listenfd,再次回到循环开头listenfd仍然用作accept的参数。accept()成功返回一个文件描述符,出错返回-1。

connect函数

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockdf:

socket文件描述符

addr:

传入参数,指定服务器端地址信息,含IP地址和端口号

addrlen:

传入参数,传入sizeof(addr)大小

返回值:

成功返回0,失败返回-1,设置errno

客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。connect()成功返回0,出错返回-1。

15、CS模型

服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。

数据传输的过程:

建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。

如果客户端没有更多的请求了,就调用close()关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。注意,任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。

在学习socket API时要注意应用程序和TCP协议层是如何交互的: 应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段 应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段

服务器端代码:

#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<ctype.h>
#include<arpa/inet.h>

#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666
int main()
{
        int lfd,cfd;
        struct sockaddr_in serv_addr;
        struct sockaddr_in clie_addr;
        socklen_t clie_addr_len;
        char buf[BUFSIZ];
        int n;
        void *dst = NULL;
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(SERV_PORT);
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        //serv_addr.sin_addr.s_addr = inet_pton(AF_INET,SERV_IP,dst);
        lfd = socket(AF_INET,SOCK_STREAM,0);

        bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));

        listen(lfd,128);
        clie_addr_len = sizeof(clie_addr);
        cfd = accept(lfd,(struct sockaddr *)&clie_addr,&clie_addr_len);

        while(1)
        {
        n = read(cfd,buf,sizeof(buf));
        int i;
        for(i = 0;i<n;i++)
        {
                buf[i] = toupper(buf[i]);
        }
        write(cfd,buf,n);
        }
        close(lfd);
        close(cfd);
        return 0;
}
~   

端口不能大于65536 小于1000的给系统来使用,用户定义使用稍微大的
INADDR_ANY 绑定一个本地的有效的任意IP
arpa/inet.h包含sockaddr这个结构体
nc命令 网络连接

nc 127.0.0.1 6666 会链接这个服务器端  前提是要./a.out 在另一个终端里面使用nc

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;
char *str;

	if (argc != 2) {
		fputs("usage: ./client message\n", stderr);
		exit(1);
	}
str = argv[1];

	sockfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	write(sockfd, str, strlen(str));

	n = read(sockfd, buf, MAXLINE);
	printf("Response from server:\n");
	write(STDOUT_FILENO, buf, n);
	close(sockfd);

	return 0;
}
由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配。注意,客户端不是不允许调用bind(),只是没有必要调用bind()固定一个端口号,服务器也不是必须调用bind(),但如果服务器不调用bind(),内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。
客户端和服务器启动后可以使用netstat命令查看链接情况:
netstat -apn|grep 6666

 

 

 

 

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值