TCP SOCKET网络编程

本文详细介绍了TCP编程的基础知识,包括服务器端和客户端的编程框架、常用函数及其应用,并提供了具体的编程实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://blog.youkuaiyun.com/hguisu/article/details/7445768/#

在文档的参考下加入了一些自己的理解。

一、TCP

1.TCP编程框架

服务器端先初始化socket,然后通过bind将socket返回的网络文件描述符和服务器的IP地址和端口号绑定起来,随后调用listen对socket返回的fd进行监听,调用accept阻塞,等待客户端接入,这是如果客户端也初始化了一个socket,然后通过connect连接服务器,此时客户端和服务器就连接成功了,然后就可以通过send/write、recv/read进行通信。

2.常用函数

2.1 socket()函数

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

* domain用于设置网络通信的域,常用的协议簇有AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)

* type用来指定socket类型,常用的有SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)

* protocol用来指定协议,常用的协议有IPPROTO_TCP、IPPTOTO_UDP,分别对应TCP传输协议、UDP传输协议,通常传0选择type类型对应的协议

2.2.1 bind()函数

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

用来将socket返回的网络文件描述符和服务器的IP地址和端口号绑定起来

* sockfd为socket返回的fd

* addr为struct sockaddr类型的结构体指针,包含服务器的IP地址和端口号

* addrlen为第二个参数的长度

2.2.2 表示IP地址的相关数据结构

* 相关定义都定义在/usr/include/netinet/in.h中

* struct in_addr
   {
           in_addr_t s_addr;

   };

* struct sockaddr_in
  {
           __SOCKADDR_COMMON (sin_);
           in_port_t sin_port;                 /* Port number.  */
           struct in_addr sin_addr;            /* Internet address.  */

          /* Pad to size of `struct sockaddr'.  */
         unsigned char sin_zero[sizeof (struct sockaddr) -
                                      __SOCKADDR_COMMON_SIZE -
                                      sizeof (in_port_t) -

                                      sizeof (struct in_addr)];

  }

* struct sockaddr 这个结构体是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等函数中都需要这个结构体,这个结构体是兼容IPV4和IPV6的。 在实际编程中这个结构体会被一个struct sockaddr_in或者一个struct sockaddr_in6所填充,即涉及到struct sockaddr的变量可以看实际使用 的是IPv4还是IPv6用sockaddr_in或者sockaddr_in6变量替代struct sockaddr_in表示使用的是IPv4,struct sockaddr_in6表示使用的是IPv6

* 在结构体中定义了端口号和IP地址,通常服务器在初始化时会通过bind给当前的fd绑定一个IP地址和端口号,由这两个元素可以识别一个特定主机中的某个应用程序/进程;而客户端通常不用指定端口号,由主机通过connect连接服务器时自动分配一个端口号和本机的IP地址组合。

* 关于网络字节序和主机字节序。即结构体中的IP地址和端口号(端口号为两字节数据时)均需要采用网络字节序。通常所说的主机字节序分为大端模式(高字节对应低地址)和小端模式(相反),网络字节序为在传输过程中低字节在前,高字节在后。实际编程中通常采用htons将端口号转换为网络字节序,通过inet_addr或者inet_pton将字符串形式的IP地址转换为32位网络字节序形式的二进制数

2.3 listen()函数

int listen(int sockfd, int backlog);

监听服务器socket返回的fd,第二个参数为相应的socket可以排队的最大连接数,socket函数创建的socket默认是主动类型的,listen将socket变为被动,等待客户端的连接请求

2.4.1 accept()函数

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

服务器阻塞等待客户端的接入,accept返回成功会返回一个连接套接字,可通过返回的连接套接字完成和客户端的通信

* sockfd为服务器socket返回的fd,属于监听套接字

* 第二个参数为输出参数,即结果参数,是一个结构体,用来输出客户端的IP地址和端口号,如果不想要可以传NULL

* 第三个参数也为输出型参数,用来指明第二个参数的大小,第二个参数为NULL时,它也传NULL

2.4.2 监听套接字和连接套接字

监听套接字:一个套接字会由主动连接的套接字经过listen后变为监听套接字,譬如accept的入参sockfd,是由服务器调用socket函数后经过listen形成的。

连接套接字:譬如accept返回的套接字,代表着网络中已经存在的点点连接,是服务器客户端相互通信的套接字。此外,连接套接字并没有占用新的端口和客户端通信,依然使用和监听套接字相同的端口号。

 一个服务器通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户端连接创建了一个已连接socket描述字,当服务器完成了对某个客户端的服务,相应的已连接socket描述字就被关闭。

2.5 connect()函数

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

函数用来完成客户端和服务器的连接

* 第一个参数为客户端通过socket函数返回的文件描述符

* 第二个参数为一个结构体,其中包含了服务器的IP地址和端口号

* 第三个参数为第二个参数的长度

2.6 send()、recv()函数

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

recv函数为从sockfd中读数据到buf中,send函数为将buf中的数据发送给sockfd

2.7 close()函数

int close(int fd);

将fd描述符标记为-1,即关闭状态,该描述符不能再作为send或recv的第一个参数进行通信,注意close操作只是使相应socket描述符为-1,只有为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

2.8 socket编程实例

服务器:

/* File Name: server.c */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define DEFAULT_PORT 8000
#define MAXLINE 4096
int main(int argc, char** argv)
{
    int    socket_fd, connect_fd;
    struct sockaddr_in     servaddr;
    char    buff[4096];
    int     n;
    //初始化Socket
    if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
    printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }
    //初始化
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址设置成INADDR_ANY,让系统自动获取本机的IP地址。
    servaddr.sin_port = htons(DEFAULT_PORT);//设置的端口为DEFAULT_PORT
 
    //将本地地址绑定到所创建的套接字上
    if( bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
    printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }
    //开始监听是否有客户端连接
    if( listen(socket_fd, 10) == -1){
    printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }
    printf("======waiting for client's request======\n");
    while(1){
        //阻塞直到有客户端连接,不然多浪费CPU资源。
        if( (connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1){
        printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
        continue;
    }
    //接受客户端传过来的数据
    n = recv(connect_fd, buff, MAXLINE, 0);
    //向客户端发送回应数据
    if(!fork()){ /*紫禁城*/
        if(send(connect_fd, "Hello,you are connected!\n", 26,0) == -1)
        perror("send error");
        close(connect_fd);
        exit(0);
    }
    buff[n] = '\0';
    printf("recv msg from client: %s\n", buff);
    close(connect_fd);
    }
    close(socket_fd);
}

客户端:

/* File Name: client.c */
 
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
 
#define MAXLINE 4096
  
int main(int argc, char** argv)
{
    int    sockfd, n,rec_len;
    char    recvline[4096], sendline[4096];
    char    buf[MAXLINE];
    struct sockaddr_in    servaddr;
 
    if( argc != 2){
    printf("usage: ./client <ipaddress>\n");
    exit(0);
    }
 
    if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
    printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
    exit(0);
    }
 
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8000);
    if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
    printf("inet_pton error for %s\n",argv[1]);
    exit(0);
    }
 
    if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
    printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }
 
    printf("send msg to server: \n");
    fgets(sendline, 4096, stdin);
    if( send(sockfd, sendline, strlen(sendline), 0) < 0)
    {
    printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
    exit(0);
    }
    if((rec_len = recv(sockfd, buf, MAXLINE,0)) == -1) {
       perror("recv error");
       exit(1);
    }
    buf[rec_len]  = '\0';
    printf("Received : %s ",buf);
    close(sockfd);
    exit(0);
}

inet_pton为Linux中的IP地址转换函数,用来将点分十进制的IP地址转换为二进制,是inet_addr的扩展

int inet_pton(int af, const char *src, void *dst);//转换字符串到网络地址:

第一个参数为协议簇,函数将第二个参数传入的IP地址转换后存到dst中。

代码测试过程:

编译server.c     gcc server.c -o server

启动进程          ./server

显示结果         ======waiting for client's request======

 

编译client.c     gcc client.c -o client

启动进程          ./client 127.0.0.1

注意:

在ubuntu 编译源代码的时候,头文件types.h可能找不到。

使用dpkg -L libc6-dev | grep types.h 查看。

如果没有,可以使用

apt-get install libc6-dev安装。

如果有了,但不在/usr/include/sys/目录下,手动把这个文件添加到这个目录下就可以了。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值