Socket编程模型

一、整体过程图解

                 

二、Socket编程模型细节

        客户端和服务器能在网络中通信,那必须得使用Socket编程,它是进程间通信里比较特别的方式,特别之处在于它是可以跨主机间通信。
创建Socket 的时候,可以指定网络层使用的是IPv4还是IPv6,传输层使用的是TCP还是UDP。服务器的程序要先跑起来,然后等待客户端的连接和数据,我们先来看看服务端的Socket编程过程是怎样的。
服务端首先调用socket()函数,创建网络协议为IPv4,以及传输协议为TCP的Socket。

接着调用bind()函数,给这个Socket绑定一个IP地址和端口,绑定这两个的目的是什么?

  • 绑定端口的目的:当内核收到TCP报文,通过TCP头里面的端口号,来找到我们的应用程序,然后把数据传递给我们。
  • 绑定IP地址的目的:一台机器是可以有多个网卡的,每个网卡都有对应的IP地址,当绑定一个网卡时,内核在收到该网卡上的包,才会发给我们;

绑定完IP地址和端口后,就可以调用listen()函数进行监听,此时对应TCP状态图中的listen,如果我们要判定服务器中一个网络程序有没有启动,可以通过netstat命令查看对应的端口号是否有被监听。


服务端进入了监听状态后,通过调用accept()函数,来从内核获取客户端的连接,如果没有客户端连接,则会阻塞等待客户端连接的到来。


那客户端是怎么发起连接的呢?客户端在创建好Socket后,调用connect()函数发起连接,该函数的参数要指明服务端的IP地址和端口号,然后万众期待的TCP三次握手就开始了。
在TCP连接的过程中,服务器的内核实际上为每个Socket维护了两个队列:

  • 一个是「还没完全建立」连接的队列,称为TCP半连接队列,这个队列都是没有完成三次握手的连接,此时服务端处于syn_rcvd 的状态;
  • 一个是「已经建立」连接的队列,称为TCP全连接队列,这个队列都是完成了三次握手的连接,此时服务端处于established 状态;

当TCP全连接队列不为空后,服务端的accept()函数,就会从内核中的TCP全连接队列里拿出一个已经完成连接的Socket返回应用程序,后续数据传输都用这个Socket。
注意,监听的Socket和真正用来传数据的Socket是两个:

  • 一个叫作监听Socket;
  • 一个叫作已连接Socket;

连接建立后,客户端和服务端就开始相互传输数据了,双方都可以通过read()和write()函数来读写数据。

三、服务端、客户端代码供参考

服务器代码:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<netinet/ip.h>
#include<arpa/inet.h> 

#define SERROPT 8000
#define SERIP "192.168.111.110"
int main(int argc, char* argv[])                                                                                                                                               
{
     //创建一个套接字 socket参数1.协议类型 2.流式套接字 3.传0(默认TCP协议)
     int lfd = socket(AF_INET, SOCK_STREAM, 0);
     struct sockaddr_in seraddr, cliaddr;
     seraddr.sin_family = AF_INET;
     seraddr.sin_port = htons(SERROPT);       //8000->16415
     seraddr.sin_addr.s_addr=INADDR_ANY;
     //seraddr.sin_addr.s_addr = 2171971776;  //192.168.117.129->2171971776
     //inet_pton(AF_INET, SERIP ,(void*)&seraddr.sin_addr.s_addr);//本地ip转网络ip函数
     //参数1.要绑定文件描述符 2.结构体包括(协议类型、端口号、地址)3.长度
     bind (lfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
     listen(lfd,64);
     //
     socklen_t clilen = sizeof(cliaddr);
     int cfd = accept(lfd , (struct sockaddr*)&cliaddr, &clilen);//第三个套接字 
     //accept是在全连接队列中取出连接 如果多次连接 全连接里没有连接可用 会发生阻塞
     char buf[1024];
     while(1)
     {
        int rr = read(cfd, buf, sizeof(buf));//读 读缓冲
        write(STDERR_FILENO, buf, rr);   //写到标准输出上
        write(cfd, buf, rr); //反射回去证明客户端已经接收到了
     }
     return 0;      
} 
   

客户端代码:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<netinet/ip.h>
#include<arpa/inet.h> 

#define SERROPT 8000
#define SERIP "192.168.111.110"
#define CLIPORT 8001
#define CLIIP "192.168.111.110" 
int main(int argc, char* argv[])                                                                                                                                               
{
     //创建一个套接字 
     int cfd = socket(AF_INET, SOCK_STREAM, 0);
     struct sockaddr_in seraddr, cliaddr;   //定义两个结构体

     cliaddr.sin_family = AF_INET;
     cliaddr.sin_port = htons(CLIPORT);      
     inet_pton(AF_INET, CLIIP ,(void*)&cliaddr.sin_addr.s_addr);
     bind (cfd, (struct sockaddr*)&cliaddr, sizeof(cliaddr));  //客户端绑定
     //
     seraddr.sin_family = AF_INET;
     seraddr.sin_port = htons(SERROPT);       
     inet_pton(AF_INET, SERIP ,(void*)&seraddr.sin_addr.s_addr);
     connect(cfd, (struct sockaddr*)&seraddr, sizeof(seraddr));//连接
         
     char buf[1024];
     while(1)
     {
        int rr = read(0, buf, sizeof(buf)); //等待输入
        write(cfd, buf, rr);  
        rr = read(cfd, buf, sizeof(buf));
        write(1, buf, rr); //反射回去证明已经接收到了
     }
     return 0;      
} 
   

        端口随便设定合理就行,IP地址(我这里随便写的IP)查看自己虚拟机IP(通过命令:ifconfig)查看,不要忘记更改,先启动服务器、再启动客户端,这样一个简单的Socket编程模型就搭建好了。

        基于Linux一切皆文件的理念,在内核中Socket也是以「文件」的形式存在的,也是有对应的文件描述符。

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值