欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux C语言实现TCP客户端与服务器

程序员文章站 2024-03-22 16:35:46
...

本文中的TCP server 和 client,为基于软件层面(TCP/IP协议栈)的Socket应用开发。

一.Socket

        套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

数据结构:注意sockaddr_in 和 sockaddr的区别(前者具体,后者抽象)

#ifdef CONFIG_NET_IPv6
struct sockaddr_storage
{
  sa_family_t ss_family;       /* Address family */
  char        ss_data[18];     /* 18-bytes of address data */
};
#else
struct sockaddr_storage
{
  sa_family_t ss_family;       /* Address family */
  char        ss_data[14];     /* 14-bytes of address data */
};
#endif

/* The sockaddr structure is used to define a socket address which is used
 * in the bind(), connect(), getpeername(), getsockname(), recvfrom(), and
 * sendto() functions.
 */

//抽象的socket地址,不区分IPV4,IPV6
//14字节 = 2(端口) + 4(ip地址) + 8(保留)
struct sockaddr
{
  sa_family_t sa_family;       /* Address family: See AF_* definitions */
  char        sa_data[14];     /* 14-bytes of address data */
};

//IPV4协议,将 2(端口) + 4(ip地址)分开表示
struct sockaddr_in
{
  sa_family_t     sin_family;  /* Address family: AF_INET */
  uint16_t        sin_port;    /* Port in network byte order */
  struct in_addr  sin_addr;    /* Internet address */
};

//IPV6
struct sockaddr_in6
{
  sa_family_t     sin6_family; /* Address family: AF_INET6 */
  uint16_t        sin6_port;   /* Port in network byte order */
  struct in6_addr sin6_addr;   /* IPv6 internet address */
};

函数接口:

int socket(int domain, int type, int protocol);
int bind(int sockfd, FAR const struct sockaddr *addr, socklen_t addrlen);
int connect(int sockfd, FAR const struct sockaddr *addr, socklen_t addrlen);

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

ssize_t send(int sockfd, FAR const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, FAR const void *buf, size_t len, int flags,
               FAR const struct sockaddr *to, socklen_t tolen);

ssize_t recv(int sockfd, FAR void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, FAR void *buf, size_t len, int flags,
                 FAR struct sockaddr *from, FAR socklen_t *fromlen);

二.TCP 与 UDP

        TCP(Transmission Control Protocol,传输控制协议是面向连接的,基于数据流的可靠协议,在正式通信之前必须建立起连接。UDP(User Data Protocol,用户数据报协议)是一个非连接的数据报协议。TCP的服务器模式比UDP的服务器模式多了listen,accept函数。TCP客户端比UDP客户端多了connect函数。

这里只谈TCP的客户端与服务器。

三.TCP server

TCP server的实现流程:

1、创建一个socket,用函数socket();

2、绑定IP地址、端口等信息到socket上,用函数bind();

3、开启监听,用函数listen();

4、接收客户端上来的连接,用函数accept();

5、收发数据,用函数send()和recv(),或者read()和write();

6、关闭网络连接;

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>

#define SERVPORT 4444
#define BACKLOG 10
#define MAXDATASIZE 15

int main() {
    struct sockaddr_in server_sockaddr;//声明服务器socket存储结构
    int sin_size,recvbytes;
    int sockfd,client_fd;//socket描述符
    char buf[MAXDATASIZE];//传输的数据

    //1.建立socket
    //AF_INET 表示IPV4
    //SOCK_STREAM 表示TCP
    if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0) {
        perror("Socket");
        exit(1);
    }

    printf("Socket successful!,sockfd=%d\n",sockfd);

    //以sockaddt_in结构体填充socket信息
    server_sockaddr.sin_family 		= AF_INET;//IPv4
    server_sockaddr.sin_port 		= htons(SERVPORT);//端口
    server_sockaddr.sin_addr.s_addr 	= INADDR_ANY;//本主机的任意IP都可以使用
    bzero(&(server_sockaddr.sin_zero),8);//保留的8字节置零

    //2.绑定 fd与 端口和地址
    if((bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))) < 0) {
        perror("bind");
        exit(-1);
    }

    printf("bind successful !\n");

    //3.监听
    if(listen(sockfd,BACKLOG) < 0) {
        perror("listen");
        exit(1);
    }

    printf("listening ... \n");

    while(1){
	//4.接收请求,函数在有客户端连接时返回一个客户端socket fd,否则则阻塞
	//优化:这里同样可以使用select,以及poll来实现异步通信
	if((client_fd = accept(sockfd,NULL,&sin_size)) == -1) {
		perror("accept");
		exit(1);
	}

	printf("accept success! client_fd:%d\n",client_fd);

	//5.接收数据
        //注意:这里传入的fd,不是建立的socket fd,而是accept返回的连接客户端 socket fd
	if((recvbytes = recv(client_fd,buf,MAXDATASIZE,0)) == -1) {
		perror("recv");
		exit(1);
	}

	printf("received data : %s\n",buf);
    }

    //6.关闭
    close(sockfd);

}

四.TCP client

TCP client的实现流程:

1、创建一个socket,用函数socket();

2、连接服务器,用函数connect();

3、收发数据,用函数send()和recv(),或者read()和write();

4、关闭网络连接;

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

#define SERVPORT 4444
 
int main(int argc,char *argv[]) {
    int sockfd,sendbytes;
    struct sockaddr_in serv_addr;//需要连接的服务器地址信息

    //1.创建socket
    //AF_INET 表示IPV4
    //SOCK_STREAM 表示TCP
    if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0) {
        perror("socket");
        exit(1);
    }

    //填充服务器地址信息
    serv_addr.sin_family 	= AF_INET; //网络层的IP协议: IPV4
    serv_addr.sin_port 		= htons(SERVPORT); //传输层的端口号
    serv_addr.sin_addr.s_addr   = inet_addr("192.168.1.xxx"); //网络层的IP地址: 实际的服务器IP地址
    bzero(&(serv_addr.sin_zero),8); //保留的8字节置零

    //2.发起对服务器的连接信息
    //三次握手,需要将sockaddr_in类型的数据结构强制转换为sockaddr
    if((connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr))) < 0) {
        perror("connect failed!");
        exit(1);
    }

    printf("connect successful! \n");

    //3.发送消息给服务器端
    if((sendbytes = send(sockfd,"hello",5,0)) < 0) {
        perror("send");
        exit(1);
    }

    printf("send successful! %d \n",sendbytes);

    //4.关闭
    close(sockfd);

}

以上是在 Linux(Ubuntu 18.04) 实现简单的TCP客户端与服务器的实例。