【网络编程笔记二】socket的概念、主要函数的应用

Sockaddr数据结构

strcut sockaddr以前是一个结构体类型,但是现在不用来做结构体了,而是作为(void*)指针,传递一个地址给函数,即:当要使用sockaddr_in 或者sockaddr_in6作为bind、connect、accept函数的参数传递时,要讲它们强制转换为strcut sockaddr类型,即(strcut sockaddr*)加函数地址。

接下来看看sockaddr_in结构体里有什么:
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family // 表示你所用选用的地址协议族
__be16 sin_port; /* Port number // 端口号
struct in_addr sin_addr; /* Internet address // IP 地址,这是一个结构体,里面只有一个成员,如下
/还有其他的成员,但是没有上面三个重要/
};

struct in_addr { /* Internet address. */
__be32 s_addr;
};

举个例子: 建立一个sockaddr_in的结构体并初始化成员:
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));//将结构体清零
servaddr.sin_family = AF_INET; //或者ipv6就赋值为AF_INET6
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //初始化IP地址,需要用大小端的转换函数,或inet_pton。网络地址设为 INADDR_ANY,这个宏表示本地的任意 IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个 IP 地址,这样设置可以在所有的 IP 地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个 IP 地址。
servaddr.sin_port = htons(6666); //初始化端口:需要用大小端的转换函数:由于这个数据包接下来应该被投放到网络当中去,所以用的是htons函数,而不是ntohs
bind( , (strcut sockaddr*)& servaddr);//在执行其他函数时,要用到该结构体作为参数,需要进行强制类型转换成strcut sockaddr

如果是要返回IP或端口号给用户自己看,那就是相反了,用的应该是ntohs或者inet_ntop函数。

socket函数

(1)
int socket(int domain, int type, int protocol);
创建一个新的套接字,并返回出可以用来访问它的文件描述符

(2)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
给套接字绑定IP和端口号(即存放IP和端口号的结构体)使 sockfd 这个用于网络通讯的文件描述符监听 addr 所描述的地址和端口号。
如果不调用bind函数的话,系统也会自动给该个终端分配一个IP地址和端口号,因此客户端可以不调用bind进行绑定,但是服务端必须调用,如果不调用的话客户端就找不到服务端了。

(3)
int listen(int sockfd, int backlog);
指定该套接字能同时连接的上限数。比如传入backlog为128,则表示同时允许128个客户与我连接,当第129个进来时,需要排队等待,等待前128个的有客户有连接完成后,才能进来连接。

(4)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
三方握手完成后,服务器调用 accept()接受连接,如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。成功返回一个新的 socket 文件描述符(注意是新的socketfd),用于和该个连接上的客户端通信,失败返回-1,设置 errno。
参数struct sockaddr *addr代表的是客户端的IP和端口,也就是说传入前不需要对它进行初始化,它是一个传出参数,返回链接客户端地址信息,含 IP 地址和端口号。
参数socklen_t *addrlen:传入传出参数(在函数中读,然后发生改变,然后改变后的量传出),传入的是调用者提供的缓冲区 addr 的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。如果给 addr 参数传 NULL,表示不关心客户端的地址。

(5)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
这是客户端调用的connect()连接服务器,传入服务端的地址结构体addr。
传入的参数int sockfd是客户端自己的,因此在客户端的程序中也需要调用socket函数建一个套接字。传入的参数const struct sockaddr *addr是服务器的。

connect 和 bind 的参数形式一致,区别在于 bind 的参数是自己的地址,
而 connect 的参数是对方的地址。

对于服务端和客户端的连接过程,作一个小结:
对于服务端:
(1) 调用socket函数创建套接字
(2) 调用bind函数绑定IP和端口号,即初始化struct sockaddr_in的后续操作
(3) 调用listen函数指定同时和该客户端连接的最大连接数
(4) 调用accept函数阻塞等待客户端发起连接
(5) 读取客户端发送来的数据
(6) 处理数据
(7) 回发数据给客户端
(8) close
对于客户端:
(1) 调用socket函数创建套接字
(2) 调用bind函数绑定IP和端口号,也可以不显示调用,依赖于“隐式绑定”
(3) 调用connect函数,传入服务端的struct sockaddr_in,对其发起连接
(4) 发送数据给服务端
(5) 读取服务端回发的数据
(6) close

代码例子:实现服务端等待连接客户端,连接成功后客户端可以给服务端发送字母,服务端接收成功后转成大写再发回客户端。

/*创建一个服务器:server.c*/
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<string.h>
#include<ctype.h>//里面放的是大小字母转换函数

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8001

int main()
{
    struct sockaddr_in serveraddr,clientaddr;//定义服务器和客户端的sockaddr数据结构
    int sockfd;//本地服务器的套接字(文件描述符)
    int confd;//连接上客户端的套接字(文件描述符)
    socklen_t client_addrlen;//客户端sockaddr数据结构的大小
    char buf[BUFSIZ] = "0";//BUFSIZ这个宏是表示系统默认的缓冲区大小
    int iError=0;

    /*1.socket*/
    sockfd = socket(AF_INET,SOCK_STREAM,0);    //创建一个socket,指定IPv4协议族_TCP协议 
    if(sockfd==-1)
    {
        printf("sockfd is error!\n");
        exit(-1);
    }

    /*2.先初始化服务器的地址结构,再进行bind*/
    bzero(&serveraddr,sizeof(serveraddr)); //将整个结构体清零
    serveraddr.sin_family = AF_INET;//选择协议族为IPv4
    serveraddr.sin_port = htons(SERVER_PORT);//绑定端口号
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地所有ip地址
    iError=bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));//绑定
    if(iError==-1)
    {
        printf("bind失败\n");
        exit(-1);
    }
    /*3.listen设定链接上限,注意此处不阻塞*/
    listen(sockfd,64);//同一时刻允许向服务器发起链接请求的数量

    /*4.accept阻塞监听客户端链接请求*/
    printf("接下来等待客户端连接本服务器,socketfd=%d,severIP=%s,severPort=%d\n",sockfd,inet_ntop(AF_INET,&serveraddr.sin_addr.s_addr,buf,sizeof(buf)),ntohs(serveraddr.sin_port));
  //  bzero(&clientaddr,sizeof(clientaddr)); //将客户端整个结构体清零
    client_addrlen = sizeof(clientaddr);
    confd = accept(sockfd,(struct sockaddr*)&clientaddr,&client_addrlen);//监听客户端链接, 会阻塞
    if(confd==-1) printf("连接失败\n");
    else printf("连接成功,客户端IP:%s,端口号:%d\n",inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,buf,sizeof(buf)),ntohs(clientaddr.sin_port));
    
    /*5.处理客户端请求,文件描述符confd*/
    while (1)
    {
        int i = 0;
        /*读取客户端发送数据*/
        int len = read(confd,buf,sizeof(buf));
        write(STDOUT_FILENO,buf,len);//输出到终端屏幕中
        /*处理客户端数据*/
        for (i=0;i<len;i++) buf[i]=toupper(buf[i]);//把从客户端发送来的数据小写转大写,并放在缓冲区中
        /*处理完数据回写给客户端*/
        write(confd,buf,len);
    }    

    /*6.关闭链接*/
    close(sockfd);
    close(confd);
    return 0;

}	


/*写一个客户端连接服务端并发送字符串过去:client.c*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>

int main(int argc, char** argv)
{
    int sockfd;
    struct sockaddr_in serveraddr;
    char buf[BUFSIZ]; 

    sockfd = socket(AF_INET,SOCK_STREAM,0);

    /*初始化一个地址结构:*/
    bzero(&serveraddr, sizeof(serveraddr));//清零
    serveraddr.sin_family = AF_INET; //IPv4协议族
    inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);//字符串类型转换为网络字节序
    serveraddr.sin_port = htons(8001); //指定端口 本地转网络字节序
    
    /*根据地址结构链接指定服务器进程*/
    printf("接下来连接服务器,sockfd=%d,serverIP=%s,serverPort=%d",sockfd,inet_ntop(AF_INET,&serveraddr.sin_addr.s_addr,buf,sizeof(buf)),ntohs(serveraddr.sin_port));
    connect(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
    while (1) 
    {            
       int len;
        /*从标准输入获取数据*/
        fgets(buf,sizeof(buf),stdin);
        /*将数据写给服务器*/
        write(sockfd,buf,strlen(buf));//写给服务器
        /*从服务器读回转换后数据*/
        len = read(sockfd,buf,sizeof(buf));
        /*写至标准输出(终端屏幕)*/ 
        write(STDOUT_FILENO, buf, len);
    }

     /*关闭链接*/        
     close(sockfd);
     return 0;
}

执行结果:
在这里插入图片描述
备注:有时候在虚拟机中执行第二次的server的时候会出现bind失败的情况,那是因为端口被占用了,此时把代码中的传入服务器结构体的端口号更改一下,或者重启一下虚拟机就可以继续连接了。

或者在终端执行命令 “netstat -anp |grep 端口号”来查看当前被占用的端口号的进程
然后用“kill -s 9 pid号” 释放进程

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值