socket编程(2) -- TCP通信

简介: socket编程(2) -- TCP通信

2. 使用 Socket 进行TCP通信

Socket通信流程图如下:
在这里插入图片描述

  这里服务器段listen是监听socket套接字的监听文件描述符。如果客户端有连接请求,服务器端会自动和客户端建立连接,这里的accept函数,只是从已经建立了连接的已连接队列中取出一个建立的客户端连接,并返回用于数据传输的文件描述符。

2.1 socket相关函数介绍

socket()

//socket函数
#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
返回值:
    成功返回socket的文件描述符,失败返回-1,并且通过设置错误信息errno
参数:
    domain:可以选取下面的参数,常用的是AF_INET,AF_INET6,AF_UNIX
        Name                Purpose                          Man page
       AF_UNIX, AF_LOCAL   Local communication              unix(7)
       AF_INET             IPv4 Internet protocols          ip(7)
       AF_INET6            IPv6 Internet protocols          ipv6(7)
       AF_IPX              IPX - Novell protocols
       AF_NETLINK          Kernel user interface device     netlink(7)
    type:可以选取下面的参数,常用的是用于tcp通信的SOCK_STREAM,和udp通信的数据包SOCK_DGRAM
        SOCK_STREAM     Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported.
           SOCK_DGRAM      Supports datagrams (connectionless, unreliable messages of a fixed maximum length).
        SOCK_SEQPACKET  Provides  a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call.
        SOCK_RAW        Provides raw network protocol access.
        SOCK_RDM        Provides a reliable datagram layer that does not guarantee ordering.
        SOCK_PACKET     Obsolete and should not be used in new programs; see packet(7).
        上面的参数还可以或上(|)下面的两个参数来添加额外属性:
        SOCK_NONBLOCK   Set the  O_NONBLOCK file status flag on the new open file description.  Using this flag saves extra calls to fcntl(2) to achieve the same result.
         SOCK_CLOEXEC   Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor.  See the  description  of  the  O_CLOEXEC flag in open(2) for reasons why this may be useful.
    protocol: 指定这个socket类型使用的协议,如果这个socket类型只有一个协议,那么这个参数设置为0

bind()

#include <sys/types.h> 
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:
    成功返回0,失败返回-1,并设置errno值
参数:
    sockfd:    使用socket函数成功返回的文件描述符
    addr:socket地址结构体,这里使用sockaddr_in结构体代替,可以接受的客户端ip和端口
        struct sockaddr {
   
   
            sa_family_t sa_family;
            char        sa_data[14];
        }

        struct sockaddr_in {
   
   
            sa_family_t    sin_family; /* address family: AF_INET */
            in_port_t      sin_port;   /* port in network byte order */
            struct in_addr sin_addr;   /* internet address */
        };
        /* Internet address. */
        struct in_addr {
   
   
            uint32_t       s_addr;     /* address in network byte order */
        };
    addrlen:sockaddr_in结构体大小,sizeof(addr)

listen()

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);
返回值:
    成功返回0,失败返回-1,并设置errno
参数:
    sockfd:socket文件描述符,同上
    backlog:排队建立3次握手队列和刚刚建立3次握手队列的链接数和,例如可以设置为1024

accept()

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
返回值:
    成功,系统掉用会返回一个非负的整数,这个整数就是已经连接的socket文件描述符,失败返回-1,并设置errno值。
参数:
    sockfd:同上
    addr:传出参数,取出的这个连接的socket文件描述符的客户端地址参数,设置为NULL表示不需要传出
    addrlen:传出地址结构体的大小, sizeof(addr),前面为NULL,则它设为NULL

connect()

#include <sys/types.h> 
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:
    成功返回0,失败返回-1,并设置errno
参数:
    sockfd: 客户端socket文件描述符
    addr: 传入参数,指定服务器的地址和端口
    addrlen: 上面结构体的大小 sizeof(addr)

2.2 TCP协议 C/S 模型

在这里插入图片描述

为了方便错误处理,可以对上面函数进行封装后使用

//wrap.h
#ifndef __WRAP_H_
#define __WRAP_H_
void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
#endif


//wrap.c
#include <wrap.h>

void perr_exit(const char *s)
{
   
   
    perror(s);
    exit(1);
}
// 确定这是一个什么类型的socket,可以接收哪种协议
int Socket(int family, int type, int protocol)
{
   
   
    int sfd;
    if ((sfd = socket(family, type, protocol)) < 0)
        perr_exit("socket error");
    return sfd;
}

// 绑定sfd的ip和端口,成功返回0,失败返回-1
int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
   
   
    int n;
    // 成功返回0
    if ((n = bind(fd, sa, salen)) < 0)
        perr_exit("bind error");
    return n;
}

// 监听sfd并自动与连接请求建立连接,监听成功返回0,失败返回-1
int Listen(int fd, int backlog)
{
   
   
    int n;
    if ((n = listen(fd, backlog)) < 0)
        perr_exit("listen error");
    return n;
}

/* 取出一个已经和服务器sfd的socket建立连接的连接队列中取出一个客户端sfd,
后两个都是传出参数,是客户端socket的信息
返回客户端文件描述符*/
int Accept(int sfd, struct sockaddr *sa, socklen_t *clientsocketlenptr)
{
   
   
    int n;
reaccept:
    if ((n = accept(sfd, sa, clientsocketlenptr)) < 0)
    {
   
   
        // 防止该阻塞函数被无关的信号打断
        if ((errno == ECONNABORTED) || (errno == EINTR))
            goto reaccept;
        else
            perr_exit("accept error");
    }
    return n;
}

/*客户端发起连接,sfd为客户端socket文件描述符,
后两个参数是服务器端的ip和端口
连接成功返回0,失败返回-1*/
int Connect(int sfd, const struct sockaddr *sa, socklen_t salen)
{
   
   
    int n;
    if ((n = connect(sfd, sa, salen)) < 0)
        perr_exit("connect error");
    return n;
}

/*从cfd文件描述符中读取数据到 buf 中
成功,返回读取到的字符串长度,如果返回0表示读到末尾,失败返回-1
*/
ssize_t Read(int cfd, void *buf, size_t buflen)
{
   
   
    ssize_t n;
readagain:
    if ((n = read(cfd, buf, buflen)) == -1)
    {
   
   
        if (errno == EINTR)
            goto readagain;
        else
            return -1;
    }
    else if (n == 0)
    {
   
   
        printf("read end of file\n");
    }

    return n;
}

ssize_t Write(int cfd, const void *buf, size_t buflen)
{
   
   
    ssize_t n;
writeagain:
    if ((n = write(cfd, buf, buflen)) == -1)
    {
   
   
        if (errno == EINTR)
            goto writeagain;
        else
            return -1;
    }
    else if (n == 0)
    {
   
   
        printf("write end of file\n");
    }
    return n;
}

int Close(int fd)
{
   
   
    int n;
    if ((n = close(fd)) == -1)
        perr_exit("close error");
    return n;
}

基础通信代码

服务器单进程处理客户端连接和数据通信,主要通过while循环来实现。

//server.c
#include <wrap.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h>

void showClient(const struct sockaddr_in *clientaddr)
{
   
   
    char buf[16];
    memset(buf, 0x00, sizeof(buf));
    inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, buf, sizeof(buf));

    printf("client family is[%d], ip is[%s] ,port is [%d]----connected\n", clientaddr->sin_family, buf, ntohs(clientaddr->sin_port));
}

int main()
{
   
   
    int sfd = Socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    bzero(&addr, 0x00);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    Bind(sfd, (struct sockaddr *)&addr, sizeof(addr));

    Listen(sfd, 1024);

    struct sockaddr_in clientaddr;
    bzero(&clientaddr, 0x00);

    int cfd;
    char buf[64];
    int n;
    socklen_t len;
    while (1)
    {
   
   
        n = 0;
        len = sizeof(clientaddr);
        cfd = Accept(sfd, (struct sockaddr *)&clientaddr, &len);
        showClient(&clientaddr);
        while (1)
        {
   
   
            memset(buf,0x00,sizeof(buf));
            n = Read(cfd, buf, sizeof(buf));
            if (n==0)
            {
   
   
                break;
            }
            printf("[%d] byte word,client send say:[%s]\n", n, buf);
            int i = 0;
            for (i = 0; i < n; i++)
            {
   
   
                buf[i] = toupper(buf[i]);
            }
            Write(cfd, buf, n);

        }
    }

    close(cfd);
    close(sfd);

    return 0;
}
//client.c
#include <wrap.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h>

void showClient(const struct sockaddr_in *clientaddr)
{
   
   
    char buf[16];
    memset(buf, 0x00, sizeof(buf));
    inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, buf, sizeof(buf));
    printf("client family is[%d], ip is[%s] ,port is [%d]----connected\n", clientaddr->sin_family, buf, clientaddr->sin_port);
}

int main()
{
   
   
    int cfd = Socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    bzero(&addr, 0x00);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);

    Connect(cfd, (struct sockaddr *)&addr, sizeof(addr));

    char words[64];
    int n;

    while (1)
    {
   
   
        memset(words, 0x00, sizeof(words));
        // scanf("%s", words);
        //读标准输入数据
        n = read(STDIN_FILENO, words, sizeof(words));

        Write(cfd, words, strlen(words));

        n = Read(cfd, words, sizeof(words));
        printf("server reply [%s],byte is [%d]\n", words, n);
    }

    return 0;
}
目录
相关文章
|
2月前
|
Python
python socket 简单通信
python socket 简单通信
42 1
|
2月前
|
网络协议 安全 网络安全
网络编程:基于socket的TCP/IP通信。
网络编程:基于socket的TCP/IP通信。
161 0
|
5天前
|
网络协议 Linux 应用服务中间件
Socket通信之网络协议基本原理
【10月更文挑战第10天】网络协议定义了机器间通信的标准格式,确保信息准确无损地传输。主要分为两种模型:OSI七层模型与TCP/IP模型。
|
22天前
|
网络协议 Linux 网络性能优化
Linux基础-socket详解、TCP/UDP
综上所述,Linux下的Socket编程是网络通信的重要组成部分,通过灵活运用TCP和UDP协议,开发者能够构建出满足不同需求的网络应用程序。掌握这些基础知识,是进行更复杂网络编程任务的基石。
45 1
|
1月前
|
网络协议 Linux 应用服务中间件
Socket通信之网络协议基本原理
【9月更文挑战第14天】网络协议是机器间交流的约定格式,确保信息准确传达。主要模型有OSI七层与TCP/IP模型,通过分层简化复杂网络环境。IP地址全局定位设备,MAC地址则在本地网络中定位。网络分层后,数据包层层封装,经由不同层次协议处理,最终通过Socket系统调用在应用层解析和响应。
|
2月前
|
网络协议 Java
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
这篇文章全面讲解了基于Socket的TCP网络编程,包括Socket基本概念、TCP编程步骤、客户端和服务端的通信过程,并通过具体代码示例展示了客户端与服务端之间的数据通信。同时,还提供了多个案例分析,如客户端发送信息给服务端、客户端发送文件给服务端以及服务端保存文件并返回确认信息给客户端的场景。
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
|
1月前
|
网络协议 Linux
TCP 和 UDP 的 Socket 调用
【9月更文挑战第6天】
|
3月前
|
Java API 开发者
Java网络编程基础与Socket通信实战
Java网络编程基础与Socket通信实战
|
3月前
|
网络协议 Java
如何在Java中使用Socket编程实现TCP连接?
在Java中,通过Socket编程实现TCP连接非常常见。以下演示了基本的TCP通信流程,可根据具体需求进行扩展。
196 0
|
3月前
|
Java 数据格式
Java面试题:简述Java Socket编程的基本流程,包括客户端和服务器的创建与通信。
Java面试题:简述Java Socket编程的基本流程,包括客户端和服务器的创建与通信。
61 0