Linux(网络编程):02---TCP网络编程

  • 服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束

服务端通信步骤如下:

  • socket();     //创建一个socket
  • bind();        //绑定IP和端口
  • listen();      //监听是否有客户端介入
  • accept();   //接受客户端的请求
  • read();      //读取客户端发来的消息
  • write();     //向客户端发送消息
  • close();    //关闭套接字

客户端通信步骤如下:

  • socket();     //创建一个socket
  • connect();   //连接某个服务端
  • read();        //读取服务端发来的消息
  • write();       //向服务端发送消息
  • close();      //关闭套接字

 一、socket()函数

  • #include<sys/types.h>
  • #include<sys/socket.h>
int socket(int protofamily, int type, int protocol);
  •  参数1:即协议域,又称为协议族(family)

域参数指定通信域;这将选择用于通信的协议系列。这些族在<sys/socket.h>中定义

AF_INET ipv4地址(32位的)与端口号(16位的)的组合
AF_INET6 IPV6的

AF_LOCAL/或者AF_UNIX(Unix域socket)

用一个绝对路径名作为地址。本地通信
AF_ROUTE  
AF_IPX IPX-Novell协议
AF_PACKET 低层包接口包
AF_NETLINK 内核用户界面设备

 参数2:指定socket类型,与第三个参数有关

套接字具有指定的类型,该类型指定通信语义

并不是所有的协议族都实现了这些协议类型,例如,AF_INET协议族就没有实现SOCK_SEQPACKET协议类型。

SOCK_STREAM 字节流套接字。提供序列化的、可靠的、双向连接的字节流。支持带外数据传输(TCP使用)
SOCK_DGRAM 数据报套接字。(UDP使用)支持数据报(固定最大长度的无连接、不可靠的消息)
SOCK_RAW 原始套接字。RAW类型,提供原始网络协议访问
SOCK_RDM 提供了一个不保证排序的可靠数据报层。不过可能数据会有乱序
SOCK_PACKET 专用类型。不能在通用程序中使用,它直接从设备驱动接受数据
SOCK_SEQPACKET 有序分组套接字。序列化包,提供一个序列化的、可靠的、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出
  •  参数3:指定协议类型

参数2的socket类型都有一个默认的协议,如果想使用默认的协议,参数3填0即可

IPPROTO_TCP TCP传输协议(SOCK_STRAM默认使用)
IPPTOTO_UDP UDP传输协议(SOCK_DGRAM默认使用)
IPPROTO_SCTP STCP传输协议
IPPROTO_TIPC TIPC传输协议

返回值:

  • socket创建成功:返回一个socket描述符(sockfd),这个描述字不是固定的,为int类型
  • socket创建失败:返回-1,并设置errno变量的值

errno变量

  • 若socket创建失败,就会设置errno为相应的值,用户可以检测errno判断出现什么错误
EACCES 没有权限建立制定的protofamily的type的socket
EAFNOSUPPORT 不支持所给的地址类型
EINVAL 不支持此协议或者协议不可用
EMFILE 进程文件表溢出
ENFILE 已经达到系统允许打开的文件数量,打开文件过多
ENOBUFS/ENOMEM 内存不足。socket只有到资源足够或者有进程释放内存
EPROTONOSUPPORT 制定的协议type在domain中不存在

什么是socket描述符???

  • 我们在使用C语言用fopen()会返回一个文件指针,这个文件指针就代表这个这个文件
  • 在Linux中,我们使用sokcet()函数打开一个socket文件,会返回一个socket描述符,这个描述符就代表着这个socket。描述符也称描述字

 二、bind()函数

  • 功能:bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4ipv6地址和端口号组合赋给socket
  • #include<sys/types.h>
  • #include<sys/socket.h>
int bind(int sockfd, const struct sockaddr  *addr, socklen_t  addrlen);
  • 参数1: 服务端的socket描述符
  • 参数2:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址(IP、端口号)
  • 参数3:对应的地址的长度。服务器在启动的时候都会绑定一个地址(如ip地址+端口号)

返回值(int类型)

  • 建立成功:成功返回0
  • 建立失败:返回-1,并设置errno变量的值

 三、listen()函数

  • 功能:作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求
  • #include<sys/types.h>
  • #include<sys/socket.h>
int listen(int sockfd, int backlog);
  • 参数1:服务端的socket描述符
  • 参数2:相应socket可以排队的最大连接个数 

返回值(int类型)

  • 建立成功:成功返回0
  • 建立失败:返回-1,并设置errno变量的值

四、connect()函数

  •  功能:客户端通过调用connect函数来建立与服务器的连接
  • #include<sys/types.h>
  • #include<sys/socket.h>
int connect(int  sockfd, const struct sockaddr  *addr, socklen_t a ddrlen);
  • 参数1:客户端的socket描述符
  • 参数2:服务器的socket地址
  • 参数3:服务器socket地址的长度

返回值(int类型)

  • 建立成功:成功返回0
  • 建立失败:返回-1,并设置errno变量的值

 五、accept()函数

  • 功能:服务器监听到这个请求之后,就会调用accept()函数去接收客户端的请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类似于普通文件的读写I/O操作 
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 参数1:服务端的socket描述符
  • 参数2:一个const struct sockaddr *指针,用来保存服务端的socket地址(IP、端口等信息)。如果不想保存服务端的信息,这个参数可以设置为NULL
  • 参数3:客户端的socket地址长度(参数2服务端指针地址大小)。如果参数2为NULL,此处也可以设置为为NULL

返回值(int类型)

  • 接收成功:返回connect_fd
  • 接收失败:返回负数

六、write()函数

  • #include<unistd.h>
  • 发送数据
ssize_t write(int fd, const void *buf, size_t count);
  • 参数1:若为服务端(为accept函数的返回值)。若为客户端(为自己socket描述符
  • 参数2:需要发送的字符串
  • 参数3:每次发送的数据的字节数

返回值(ssize_t类型)

  • 传递成功:返回发送的数据的字节大小
  • 传递失败:返回-1,并设置errno变量的值

errno变量

  • EINTR:表示在写的时候出现了中断错误
  • EPIPE:网络连接出现了问题(对方已经关闭了连接) 

 七、read()函数

  • #include<unistd.h>
  • 接收数据
ssize_t read(int fd, void *buf, size_t count);
  • 参数1:若为服务端(为accept函数的返回值)。若为客户端(为自己socket描述符
  • 参数2:为接收数据的缓冲区
  • 参数3:每次接收数据的字节数

返回值

  • 成功:返回接收的数据的字节大小
  • 失败:返回-1,并设置errno变量的值

errno变量

  • EINTR:说明读是由中断引起的
  • ECONNREST:表示网络连接出了问题 

八、close()函数

  • 关闭相应的socket描述符
  • #include <unistd.h>
int close(int fd);
  •  参数:对应的socket描述符

服务端

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


static void usage( const char* proc )
{
    printf("Please use %s [ip] [port]\n",proc);
}

void thread_run( void * arg )
{
    printf("create a new thread\n");

    int fd=(int)arg;
    char buf[1024];
    
    while(1)
    {
        memset(buf,'\0',sizeof(buf));
        ssize_t _s=read(fd,buf,sizeof(buf)-1); //读取客户端的数据
        if(_s>0)
        {
            buf[_s]='\0';
            printf("client say : %s\n",buf);
        }
        memset(buf,'\0',sizeof(buf));
        printf("please write :");
        fflush(stdout);
        ssize_t _s2=read(0,buf,sizeof(buf)-1);//输入数据
        if(_s2>0){
            write(fd,buf,strlen(buf));//放出去
        }
    }
}

int main( int argc,char *argv[] )
{
    if(argc!=3){
        usage(argv[0]);
        exit(0);
    }

    //1.create socket
    int sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sock<0){
        printf("Create socket error\n");
        return 1;
    }
    printf("Create socket success\n");

    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(atoi(argv[2]));
    local.sin_addr.s_addr=inet_addr(argv[1]);

    //2.bind
    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
        printf("Bind error\n");
        close(sock);
        return 2;
    }
    printf("Bind success\n");

    //3.listen
    if(listen(sock,10)<0){
        printf("Listen error\n");
        close(sock);
        return 3;
    }
    printf("Listen success\n");

    //4.accept
    struct sockaddr_in peer;
    socklen_t len=sizeof(peer);
    while(1){   //不断接受客户端的接入
        int fd=accept(sock,(struct sockaddr*)&peer,&len);
        if(fd<0){
            perror("accept error\n");
            close(sock);
            return 4;
        }

        printf("get connect,client ip is %s port is %d\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));

        pthread_t id;
        pthread_create(&id,NULL,thread_run,(void*)fd); //有客户端接入,就创建新的线程
        pthread_detach(id);
        
    }
    close(sock);
    return 0;
}

客户端

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

void usage( const char *proc )
{
    printf("Please use %s [ip] [port]\n",proc);
}

int main( int argc, char *argv[] )
{
    if(argc!=3){
        usage(argv[0]);
        exit(0);
    }

    //1.create socket
    int sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sock<0){
        printf("Create socket error\n");
        return 1;
    }

    struct sockaddr_in remote;
    remote.sin_family=AF_INET;
    remote.sin_port=htons(atoi(argv[2]));
    remote.sin_addr.s_addr=inet_addr(argv[1]);

    //2.connect
    if(connect(sock,(struct sockaddr*)&remote,sizeof(remote))<0){
        printf("Connect failed,reasons:%s\n",strerror(errno));
        close(sock);
        return 2;
    }

    printf("Connect success\n");

    char buf[1024];
    while(1){
        memset(buf,'\0',sizeof(buf));
        printf("Please write:");
        fflush(stdout);
        ssize_t _s=read(0,buf,sizeof(buf)-1); //输入要发送的数据
        if(_s>0){
            buf[_s-1]='\0';
            write(sock,buf,strlen(buf));//发送数据
            _s=read(sock,buf,sizeof(buf)-1);//接受数据
            if(_s>0){
                if(strncasecmp(buf,"quit",4)==0){
                    printf("
quit\n");
                    break;
                }
                buf[_s-1]='\0';
                printf("read is:%s\n",buf);
            }
        }
    }
    close(sock);
}