socket 基础

Socket是什么呢?

① Socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过“套接字”向网络发出请求或者应答网络请求。

② Socket是连接运行在网络上的两个程序间的双向通信的端点。

③ 网络通讯其实指的就是Socket间的通讯。

④ 通讯的两端都有Socket,数据在两个Socket之间通过IO来进行传输。

或者更通俗的解释: socket = ip + port

Socket原理?

基于tcp连接的socket 连接图
在这里插入图片描述
在这里插入图片描述

流式传输:“客户端”,
1.socket()函数;
2.bind()函数可有可无,加上指定传输端口,不加随机分配端口;
3.connect()函数,填写服务端的地址与端口【网络间通信AF_STREAM】或者路径【进程间通信AF_DGRAM】;
4.send()函数;
5.recv()函数。

流式传输:“服务端”,
1.socket()函数;
2.bind()函数,必须加上指定传输端口【网络间通信AF_STREAM】或者是路径【进程间通信AF_DGRAM】;
3.listen()函数,使用isockfd;
5.accepc()函数,生成新的fd,inewfd;
6.send()函数,inewfd;
7.recv()函数,inewfd。

socket 接口函数

#include<sys/socket.h>

//socket 函数
int socket(int domain, int type, int protocol)

    第一个参数domain指明了协议族,通常用AF_INET、AF_INET6、AF_LOCAL等。AF表示地址族,选择 AF_INET 的目的就是使用 IPv4 进行通信。因为 IPv4 使用 32 位地址,相比 IPv6 的 128 位来说,计算更快,便于用于局域网通信。
    第二个参数type是Socket类型,常用的Socket类型我们之前已经介绍过了分别是SOCK_STREAM和SOCK_DGRAM因为我们要写的是TCP Socket编程所以我们使用SOCK_STREAM。
    第三个参数protocol表示传输协议一般取为0。因为一般情况下有了 domain和 type 两个参数就可以创建套接字了,操作系统会自动推演出协议类型,除非遇到这样的情况:有两种不同的协议支持同一种地址类型和数据传输类型。如果我们不指明使用哪种协议,操作系统是没办法自动推演的。
    
    example:
    int sockfd = socket(AF_INET, SOCK_STREAM, 0); //建立套接字,基于TCP
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0); //基于UDP
/=====================================================================/

//bind  函数  
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 
    
    第一个参数sockfd为上一步创建socket时的返回值。
    第二个参数addr 为 sockaddr 结构体变量的指针。该类型的定义原型如下:

    struct sockaddr_in {
        short   sin_family;    //协议族,与前面Socket函数中提到的一样,我们这里使用AF_INET
        u_short sin_port;        //端口号,需要
        struct in_addr sin_addr;    //IP地址,需要使用网络序
        char    sin_zero[8];    //没有实际意义,只是为了跟SOCKADDR结构在内存中对齐
    };
    第三个参数addrlen为addr 变量的大小,可由 sizeof() 计算得出。
    
    example:
    struct sockaddr_in serv_addr;    //创建结构体变量
    servaddr.sin_family=AF_INET;    //sin_family指代协议族和前面讲述socket()的第一个参数的含义相同,取值也需跟socke函数第一个参数值一样。
    servaddr.sin_port=htons(2000);    //sin_port存储端口号(使用网络字节顺序,对于htons()函数我们还有单独一章的说明,2000这个端口转换为网络字节序列。
    //理论上端口号的取值范围为是0到65536,但0到1023的端口一般由系统分配给特定的服务程序,比如Web 服务的端口号为 80所以我们的程序要尽量在 1024~65536 之间分配端口号。
 
    servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");    //将iP地址127.0.0.1也就是本机地址转换为十进制
    bind(sockfd,(sockaddr*)&servaddr,sizeof(servaddr));    // 将套接字绑定到本地地址和端口上。
/=====================================================================/

//listen函数
int listen(int sockfd, int n);

    第一个参数为第一步sockfd创建socket时的返回值,套接字的描述符。
    第二个参数n用于指定接收队列的长度,也就是在Server程序调用accept函数之前最大允许进入的连接请求数,多余的连接请求将被拒绝,典型取值为5。

    example:
    listen(sockfd,5);//监听sockfd为创建套接字时的返回值。
/=====================================================================/
 
//accept函数        
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    
    sockfd为建立socket函数返回的值。
    addr为 sockaddr 结构体变量的指针,这个参数是指针类型,是向外传内容的,即addr将在函数调用后填入对方(客户端)的地址信息,如对方的IP、端口等。
    addrlen为 addr变量的大小,可由 sizeof() 计算得出。   
    
    example:
    struct sockaddr_in clientaddr//创建客户端地址结构体
    int aID;//用来接收accept函数返回值
    aID = accept(sockfd,(sockaddr*)&clientaddr, sizeof( clientaddr));//等待接收客户连接请求
/=====================================================================/
        
//connect函数 
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    sockfd:socket文件描述符
    addr:传出参数,返回链接客户端地址信息,含IP地址和端口号
    addrlen:传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
    
    example:
    struct sockaddr_in server_addr;
    int cfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    inet_pton(AF_INET, CLIENT_IP, &server_addr.sin_addr.s_addr);
    server_addr.sin_port = htons(6666);
    connect(cfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
/=====================================================================/
        
//recv函数(tcp) & recvfrom( 通常用于UDP)
int recv (int fd, void *buf, int n, int flags);
    
    第一个参数fd,表示连接成功的套接字描述符。
    注意:这一步对于服务端而言是上一步accept的返回值;对于客户端而言是connect的返回值,并非是第一步socket创建套接字的返回值,不要搞混!
    第二个参数buf,就是为要接收的数据所在的缓冲区地址,也就是一个空的字符数组的首地址,这里放结果。
    第三个参数len为要接收数据的字节数。
    第四个参数flags为发送数据时的附带标记 ,一般情况下设置为0。但可以选择下列设置:
    MSG_DONTROUTE:表示不使用指定路由,对send、sendto有效
    MSG_PEEK:对recv, recvfrom有效,表示读出网络数据后不清除已读的数据
    MSG_OOB:对发送接收都有效,表示发送或接受加急数据
    
    example:
    char recBuf[200];//定义一个字符串用来保存客户端发来的数据
    recv(fd,recBuf,200,0);//接收来自客户端或服务端的数据
    recv缺省是阻塞函数,直到收到信息或出错才会返回
/=====================================================================/
        
//send函数(tcp) & sendto(通常用于UDP)
int send (int fd, const void *buf, int n, int flags);
    
    第一个参数fd,表示连接成功的套接字描述符。
    注意:这一步对于服务端而言是上一步accept的返回值;对于客户端而言是connect的返回值,并非是第一步socket创建套接字的返回值,不要搞混!
    第二个参数buf为要发送的数据所在的缓冲区地址,即一个已经存好内容的字符数组
    第三个参数len为要发送的数据的实际字节数+1。
    第四个参数flags为发送数据时的附带标记 ,一般情况下设置为0。但可以选择下列设置:
    MSG_DONTROUTE:表示不使用指定路由,对send、sendto有效 
    MSG_PEEK:对recv, recvfrom有效,表示读出网络数据后不清除已读的数据 
    MSG_OOB:对发送接收都有效,表示发送或接受加急数据
    
    example:
    char sendBuf[200];//定义一个数组用来保存发送的数据
    send(fd,sendBuf,strlen(sendBuf)+1,0);//用来发送服务端或客户端的数据
    与recv同样,send函数缺省也是阻塞函数,直到发送完毕或出错才会返回。
    需要注意,如果函数返回值与参数len不相等,则剩余未发送的信息需要再次发送
    
/=====================================================================/
        
//close函数 && shutdown函数 
int close (int fd);
int shutdown (int fd, int how)    
     /* how determines what to shut down:
     SHUT_RD   = No more receptions;
     SHUT_WR   = No more transmissions;
     SHUT_RDWR = No more receptions or transmissions.*/
        
/=====================================================================/    

socket 类似tcp 的工作原理

在这里插入图片描述
在这里插入图片描述

基于TCP(面向连接)的socket编程——流式套接字(SOCK_STREAM)

server.c
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>

#define PORT 23		//端口号
#define BACKLOG 5	//最大监听数

int main()
{
    int iSocketFD = 0;  //socket句柄
    int iRecvLen = 0;   //接收成功后的返回值
    int new_fd = 0; 	//建立连接后的句柄
    char buf[4096] = {0}; //
    struct sockaddr_in stLocalAddr = {0}; //本地地址信息结构图,下面有具体的属性赋值
    struct sockaddr_in stRemoteAddr = {0}; //对方地址信息
    socklen_t socklen = 0;
    
    //建立socket
    iSocketFD = socket(AF_INET, SOCK_STREAM, 0); 
    if(0 > iSocketFD)
    {
        printf("创建socket失败!\n");
        return 0;
    }	
    
    stLocalAddr.sin_family = AF_INET;  /*该属性表示接收本机或其他机器传输*/
    stLocalAddr.sin_port = htons(PORT); /*端口号*/
    stLocalAddr.sin_addr.s_addr=htonl(INADDR_ANY); /*IP,括号内容表示本机IP*/
    
    //绑定地址结构体和socket
    if(0 > bind(iSocketFD, (void *)&stLocalAddr, sizeof(stLocalAddr)))
    {
        printf("绑定失败!\n");
        return 0;
    }
    
    //开启监听 ,第二个参数是最大监听数
    if(0 > listen(iSocketFD, BACKLOG))
    {
        printf("监听失败!\n");
        return 0;
    }
    
    printf("iSocketFD: %d\n", iSocketFD);	
    //在这里阻塞知道接收到消息,参数分别是socket句柄,接收到的地址信息以及大小 
    new_fd = accept(iSocketFD, (void *)&stRemoteAddr, &socklen);
    if(0 > new_fd)
    {
        printf("接收失败!\n");
        return 0;
    }else{
        printf("接收成功!\n");
        //发送内容,参数分别是连接句柄,内容,大小,其他信息(设为0即可) 
        send(new_fd, "这是服务器接收成功后发回的信息!", sizeof("这是服务器接收成功后发回的信息!"), 0);
    }
    
    printf("new_fd: %d\n", new_fd);	
    iRecvLen = recv(new_fd, buf, sizeof(buf), 0);	
    if(0 >= iRecvLen)    //对端关闭连接 返回0
    {	
        printf("接收失败或者对端关闭连接!\n");
    }else{
        printf("buf: %s\n", buf);
    }
    
    close(new_fd);
    close(iSocketFD);
    
    return 0;
}
client.c
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
 
#define PORT 23			//目标地址端口号
#define ADDR "192.168.1.230" //目标地址IP
 
int main()
{
    int iSocketFD = 0; //socket句柄
    unsigned int iRemoteAddr = 0;
    struct sockaddr_in stRemoteAddr = {0}; //对端,即目标地址信息
    socklen_t socklen = 0;  	
    char buf[4096] = {0}; //存储接收到的数据
    
    //建立socket
    iSocketFD = socket(AF_INET, SOCK_STREAM, 0); 
    if(0 > iSocketFD)
    {
        printf("创建socket失败!\n");
        return 0;
    }	
    //绑定服务器的ip地址和端口
    stRemoteAddr.sin_family = AF_INET;
    stRemoteAddr.sin_port = htons(PORT);
    inet_pton(AF_INET, ADDR, &iRemoteAddr);
    stRemoteAddr.sin_addr.s_addr=iRemoteAddr;
    
    //connet()连接方法: 传入句柄,目标地址,和大小
    if(0 > connect(iSocketFD, (void *)&stRemoteAddr, sizeof(stRemoteAddr)))
    {
        printf("连接失败!\n");
        //printf("connect failed:%d",errno);//失败时也可打印errno
    }else{
        printf("连接成功!\n");
        recv(iSocketFD, buf, sizeof(buf), 0);//将接收数据打入buf,参数分别是句柄,储存处,最大长度,其他信息(设为0即可)。 
        printf("Received:%s\n", buf);
    }
    
    close(iSocketFD);//关闭socket	
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值