Socket编程实践(2) Socket API 与 简单例程

在本篇文章中,先介绍一下Socket编程的一些API,然后利用这些API实现一个客户端-服务器模型的一个简单通信例程。该例子中,服务器接收到客户端的信息后,将信息重新发送给客户端。

socket()函数

socket()函数用于创建一个套接字。这就好像购买了一个电话。不过该电话还没有分配号码。

 #include <sys/types.h>         

 #include <sys/socket.h>



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

参数说明:

  • domain:指定通信的协议族,这些协议族定义在头文件< sys/socket.h >中。使用IPV4协议族时,该参数设置为AF_INET
  • type :指定socket的类型。在上一篇文章中介绍过,套接字常用的有三种类型:流式套接字SOCK_STREAM,数据报套接字SOCK_DGRAM,原始套接字SOCK_RAW
  • protocol : 该参数指定了一种协议类型用于所选择的套接字。如果仅有一种协议支持某种套接字类型,那么该参数可以定义为0,此时使用默认协议;如果一种套接字类型可能有多种协议类型,那么必须显式指定协议类型。关于具体细节,可以man socket进行查阅。

socket()的返回值:成功时返回非负整数;失败时返回-1;

bind() 函数

bind()函数绑定一个本地地址到套接字上,这相当于为电话绑定了号码。当一个套接字通过socket()被创建,它并没有绑定到具体的地址上,bind()来完成这个步骤。 bind()函数的函数原型如下:

 #include <sys/types.h>    

 #include <sys/socket.h>



int bind(int sockfd, const struct sockaddr *addr,

        socklen_t addrlen);

参数说明:

  • sockfd:socket()函数创建后成功返回的套接字
  • addr : 需要绑定的地址
  • addrlen:套接字的大小

这里需要使用到sockaddr_in结构来表示一个地址,该结构如下:

struct sockaddr_in

{

sa_family_t  sin_family;

in_port_t    sin_port;

struct in_addr sin_addr;

};

struct in_addr

{

uint32_t s_addr;

}

sockaddr_in需要强制转换为struct sockaddr*类型,传递给bind()函数的第二个参数。下面是一段例程:

int main()

{

int listenfd = socket(AF_INET,SOCK_STREAM,0);

if(listenfd == -1)

err_exit("socket error");

struct sockaddr_in addr;

//填充结构

addr.sin_family  = AF_INET;

addr.sin_port= htons(8001); //主机字节序转换为网络字节序

addr.sin_addr= htonl(INADDR_ANY);//绑定主机的任一个IP地址

/*下面两句具有相同的功能:都是绑定到本机ip地址*/

//inet_aton("127.0.0.1",&addr.sin_addr);

//addr.sin_addr.s_addr = inet_addr("127.0.0.1");



if(bind(listenfd,(const struct sockaddr*)&addr,sizeof(addr))==-1)

err_exit("bind error");

}

listen()函数

当使用socket()创建了一个套接字时,该套接字默认是主动套接字。使用listen()函数会使套接字称为一个被动套接字,也就是说,该套接字将被用来接受连接的数据,这些数据通过accept()函数接收。

listen()函数的函数原型如下:

 #include <sys/types.h>         

 #include <sys/socket.h>

 int listen(int sockfd, int backlog);

参数说明:

  • sockfd : 套接字。
  • backlog: 指定连接队列的长度。

对于给定的监听套接字,内核需要维护两个队列:

  1. 已完成连接队列:该队列中的连接处于ESTABLISHED状态,也即是已经完成了三次握手过程。
  2. 未完成连接队列:该队列中的连接处于SYN_RCVD状态,还未建立连接。

两个队列的长度之和不能够超过backlogi。如果一个连接请求到达时未完成队列已满,客户端可能接收到一个错误指示ECONNREFUSED。服务器使用accept()函数从已完成连接队列的队头返回一个连接。下面是TCP为监听套接口维护的两个队列:

accept()函数

accept()函数用于从已完成队列的队头返回一个连接。它的函数原型为:

#include <sys/types.h>          

#include <sys/socket.h>



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

参数说明:

  • sockfd : 服务器套接字
  • addr :用于接收对等方(客户端)的套接字地址。该参数填充为NULL时,不接收任何信息。
  • addrlen:返回对等方的套接字地址长度。如果不关心可以设置为NULL,否则一定要初始化。

函数返回值:成功返回一个非负整数,代表一个套接字;失败返回-1;

connect()函数

该函数用于建立一个连接到指定的套接字。函数的原型为:

#include <sys/types.h>      

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,

                   socklen_t addrlen);

参数说明:

  • sockfd : 未连接的套接字
  • addr:未连接的套接字地址
  • addrlen:addr的长度

一个简单的socket 通信例程

客户端代码:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>

#define ERR_EXIT(m)\
        do \
        {\
                perror(m);\
                exit(EXIT_FAILURE);\
        }while(0)

int main()
{
        /*创建一个套接字*/
        int sock = socket(AF_INET,SOCK_STREAM,0);
        if(sock == -1)
              ERR_EXIT("socket");

        /*定义一个地址结构*/
        struct sockaddr_in servaddr;
        memset(&servaddr,0,sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5888);
        servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

        /*进行连接*/
        if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
        {
                ERR_EXIT("connect");
        }
        else
        {
                printf("连接成功\n");
        }
        char sendbuf[1024]={0};
        char recvbuf[1024]={0};
        /*从标准输入中读入*/
        while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
        {
                write(sock ,sendbuf,strlen(sendbuf));
                if(read (sock,recvbuf,sizeof(recvbuf))>0)
                {
                        printf("从服务器接收信息:\n");
                        fputs(recvbuf,stdout);
                }
                memset(&sendbuf,0,sizeof(sendbuf));
                memset(&recvbuf,0,sizeof(recvbuf));
        }
        close(sock);
        return 0;
    } 

服务器端代码:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<string.h>
#define ERR_EXIT(m)\
        do \
        {\
                perror(m);\
                exit(EXIT_FAILURE);\
        }while(0)

int main()
{
        /* 创建一个套接字*/

        int listenfd= socket(AF_INET ,SOCK_STREAM,0);
        if(listenfd==-1)
                ERR_EXIT("socket");

        /*定义一个地址结构并填充*/
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;   //协议族为ipv4
        addr.sin_port = htons(5888); //绑定端口号
        addr.sin_addr.s_addr = htonl(INADDR_ANY);//主机字节序转为网络字节序

        /*将套接字绑定到地址上*/
        if(bind(listenfd,(const struct sockaddr *)&addr ,sizeof(addr))==-1)
        {
                ERR_EXIT("bind");
        }
        /*监听套接字,成为被动套接字*/
        if(listen(listenfd,SOMAXCONN)<0)
        {
                ERR_EXIT("Listen");
        }
        struct sockaddr_in peeraddr;
        socklen_t peerlen = sizeof(peeraddr);

        /*定义一个套接字,通常称为已连接套接字*/
        int conn ;
        conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);
        if(conn <0)
            ERR_EXIT("accept error");
        else
            printf("连接到服务器的客户端的IP地址是:%s,端口号是:%d\n",inet_ntoa(peeraddr.sin_addr),htons(peeraddr.sin_port));

        /*循环获取数据、发送数据*/
        char recvbuf[1024];
        while(1)
        {
                memset(recvbuf,0,sizeof(recvbuf));
                int ret = read(conn,recvbuf ,sizeof(recvbuf));
                fputs(recvbuf,stdout);
                write(conn,recvbuf,sizeof(recvbuf));
        }
        /*关闭套接字*/
        close(listenfd);
        close(conn);
        return 0;
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏刘望舒

Android网络编程(十)Retrofit2后篇[注解]

前言 在上一篇Android网络编程(九)Retrofit2前篇[基本使用]中我们了解了Retrofit的最基本的GET方式访问网络的写法以及请求参数的简单介绍...

1946
来自专栏技术小黑屋

Error-prone,Google出品的Java和Android Bug分析利器

Error-prone是基于BugPattern来发现问题的,覆盖范围不仅限于Java还包含Android代码。一些比较常见的BugPattern有如下这些

2882
来自专栏滕先生的博客

socket的简单使用概念socket通信过程,使用步骤:导入头文件创建socket函数connect连接到服务器发送数据接收服务器返回的数据关闭连接例子:请求百度

3016
来自专栏精讲JAVA

java面试线程必备知识点,怼死面试官,从我做起

内存屏障:限制命令操作顺序,有LoadLoad、LoadStore、LoadStore、StroreStreo四种屏障

1344
来自专栏禁心尽力

solr_架构案例【京东站内搜索】(附程序源代码)

注意事项:首先要保证部署solr服务的Tomcat容器和检索solr服务中数据的Tomcat容器,它们的端口号不能发生冲突,否则web程序是不可能运行起来的。 ...

2477
来自专栏Kirito的技术分享

Spring Boot Dubbo应用启停源码分析

Dubbo Spring Boot 工程致力于简化 Dubbo RPC 框架在Spring Boot应用场景的开发。同时也整合了 Spring Boot 特性:

1512
来自专栏Java帮帮-微信公众号-技术文章全总结

SpringMVC框架复习大纲【面试+提高】

2074
来自专栏刘望舒

Retrofit2与服务端实例讲解

网络上对 Retrofit2 的各种介绍文章已经很多了,不过往往只是对其用法进行介绍,而缺少相应的实践,这一方面是因为网络上的免费API接口返回的数据格式和访问...

1133
来自专栏芋道源码1024

源码级别解读 mybatis 插件

简介: ? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以...

3778
来自专栏Laoqi's Linux运维专列

python3–内置模块

3866

扫码关注云+社区