linux网络编程 -- tcp和udp通信的简单实现

1、整个流程图

图片来自《高质量嵌入式linux c编程第2版 第355页》
在这里插入图片描述

2、创建:socket()

  • socket()用于创建一个通信的端口,并返回指向该端口的文件描述符。
 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>

 int socket(int domain, int type, int protocol);
  • domain :指定地址类型
    AF_INET :ipv4
    AF_INET6 :ipv6
  • type:指定通信类型
    SOCK_STREAM:提供双向连续且可信赖的数据流一般用于tcp通信
    SOCK_DGRAM:提供不可信不连续的数据包连接一般用于udp通信
  • protocol:指定所使用协议的编号,一般为0
  • 成功:
    返回文件描述符,并且是当前进程未打开的编号最低的文件描述符。
  • 失败:
    -1

socket参数的可选值还有很多,这里只选取tcp和udp常用的

  • 示例
// ipv4 tcp
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1){
    perror("socket");
    exit(-1);
    }

3、公布地址:bind()

  • bind()为套接字分配名称,公布自己是谁
  • 将addr指定的地址分配给文件描述符sockfd引用的套接字。
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,
         socklen_t addrlen);
  • sockfd:socket返回的文件描述符
  • addr:详见:sockaddr和sockaddr_in
  • addrlen:一般是sizeof(struct sockaddr)
  • 成功:返回0
  • 失败:返回 -1
  • 示例
int ret = 0;
struct sockaddr_in addr;
memset(&addr,0, sizeof(struct sockaddr));//格式化
//bzero(&addr,sizeof(struct sockaddr))

addr.sin_family = AF_INET;//ipv4
addr.sin_port = htons(2233);//如果端口为0,会自动选择应该未被占用的
addr.sin_addr.s_addr = inet_addr("192.168.200.100");//将地址转为二进制
//addr.sin_addr.s_addr = inet_addr(INADDR_ANY);//自动填充为本机地址

ret = bind(sock_fd,(struct sockaddr *)&addr, sizeof(struct sockaddr);
if( ret == -1){
	perror("bind error");
	close(sock_fd);
	exit(-1);
}

转换函数详见:网络字节序与主机字节序的转化、ipv4地址与二进制数字的转化

4、设置监听:listen()

  • listen()将socket设置为监听模式,等待连接
 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>

 int listen(int sockfd, int backlog);
  • sockfd:套接字
  • backlog:能同时处理的最大连接请求
    如果连接请求已满,客户端可能会收到ECONNREFUSED的错误
    如果底层协议支持重传,则可能会忽略该请求,以便稍后在连接成功时重新尝试。

成功返回0 失败返回-1

  • 示例
if(listen(sock_fd,5) == -1){
       perror("listen error");
       close(sock_fd);
       exit(-1);
   }

5、接受请求:accept()

  • accept()用于接受连接请求
 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>

 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd:套接字
  • addr:用于保存请求者的地址数据
    详见:sockaddr和sockaddr_in
  • addrlen:一般是保存 sizeof(struct sockaddr)变量的地址

成功返回新的套接字 失败返回-1

  • 示例
int new_fd = 0;
struct sockaddr_in addr;
unsigned int addrlen = sizeof(struct sockaddr);
memset(&addr,0, sizeof(struct sockaddr_in));

new_fd = accept(sock_fd, (struct sockaddr *)&addr, &addrlen);
//new_fd = accept(sock_fd, NULL, NULL);//不考虑请求方的信息
if(new_fd == -1){
    perror("accept error");
    close(sock_fd);
    exit(-1);
}
//打印请求者的地址和端口
printf("%s %d connect\n", inet_ntoa(addr.sin_addr),
       ntohs(addr.sin_port));

6、请求连接:connect()

  • connect()用于客户端向服务器发起连接请求
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,
            socklen_t addrlen);
  • sockfd:套接字
  • addr:传入主机的地址信息
    详见:sockaddr和sockaddr_in
  • addrlen:一般是 sizeof(struct sockaddr)

成功返回0 失败返回-1

  • 示例
int sock_fd = socket(AF_INET,SOCK_STREAM,0);
if(sock_fd == -1){
    perror("socket error");
    exit(-1);
}

int ret = 0;
struct sockaddr_in addr;
memset(&addr,0, sizeof(struct sockaddr_in));

addr.sin_family = AF_INET;//ipv4
addr.sin_port = htons(2233);//主机端口
addr.sin_addr.s_addr = inet_addr("192.168.200.100");//主机地址

ret = connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr));
if(ret == -1){
    perror("connect error");
    close(sock_fd);
    exit(-1) ;
}   

7、数据发送:send(),sendto()

  • send一般用于tcp,sendto一般用于udp
#include <sys/types.h>
#include <sys/socket.h>

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

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
  • sockfd:套接字
  • buf:要发送的数据
  • len:要发送数据的长度
  • flags:一般为0
  • dest_addr:接收方的端口信息
  • addrlen:sizeof(struct sockaddr)
  • 成功
    返回实际发送的字节数
  • 失败
    返回-1
  • 示例
/* send */
if(send(sock_fd,"hello",6,0) == -1){
    perror("send");
    return -1;
}

/* sendto */
int ret = 0;
struct sockaddr_in dest;
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0)

memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(2233);
dest.sin_addr.s_addr = inet_addr("127.0.0.1");

ret = sendto(sock_fd, "hello", 5, 0,
       (struct sockaddr *)&dest, sizeof(dest));

if(ret == -1){
    perror("sendto");
    return -1;
}

8、数据接收:recv(),recvfrom()

  • recv一般用于tcp,recvfrom一般用于udp
  • 若没收到消息则会阻塞,除非套接字是非阻塞的
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
  • sockfd:套接字
  • buf:用于保存接收的数据,可以为结构体类型
  • len:要接收数据的长度
  • flags:一般为0
  • dest_addr:发送方的端口信息
  • addrlen:sizeof(struct sockaddr)
  • 成功
    返回实际接收的字节数
  • 失败
    返回-1
  • 示例
/* recv */
char buf[2048];
int ret;

memset(buf, 0, sizeof(buf));
ret = recv(new_fd, buf, sizeof(buf) - 1, 0);
if(ret == -1){
	perror("recv error")
	return -1;
}
buf[ret] = 0;//防止打印乱码
puts(buf);

/* recvfrom */
int num;
char buf[1024];
struct sockaddr_in remote;
num = recvfrom(sock_fd, buf, sizeof(buf)-1, 0,
                   (struct sockaddr *)&remote, &len);
if (num == -1) {
    perror("recv from");
    break;
}

9、tcp通信实例

tcp_fun.h

#ifndef TCP_FUN_H
#define TCP_FUN_H

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

int tcp_server_init(const char *ip,int port);//服务器初始化
int tcp_accept(int sock_fd);//服务器接受请求
void tcp_server_chat(int new_fd);//服务器消息传输

int tcp_client_init(const char *ip,int port);//客户端初始化
void tcp_client_chat(int sock_fd);//服务器消息传输
#endif

tcp_fun.c

#include "tcp_fun.h"

/* 有几个函数不应该用exit(-1) 这个会让程序直接结束,但是懒得改了 */
int tcp_server_init(const char *ip,int port){
    int sock_fd = socket(AF_INET,SOCK_STREAM,0);
    if(sock_fd == -1){
        perror("socket error");
        exit(-1);
    }

    // 配置监听描述符地址复用属性
    //可不配置,主要涉及到4次挥手
    int opt = 1;
    int ret = setsockopt(sock_fd,SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));
    if (ret < 0) {
        perror("set socket opt error");
        exit(-1);
    }

    ret = 0;
    struct sockaddr_in addr;
    memset(&addr,0, sizeof(struct sockaddr_in));
    //bzero(&addr,sizeof(struct sockaddr_in))

    addr.sin_family = AF_INET;//ipv4
    addr.sin_port = htons(port);//端口
    addr.sin_addr.s_addr = inet_addr(ip);
    //addr.sin_addr.s_addr = INADDR_ANY;//自动填充为本机地址

    ret = bind(sock_fd,(struct sockaddr *)&addr, sizeof(struct sockaddr));
    if( ret == -1){
        perror("bind error");
        close(sock_fd);
        exit(-1);
    }

    if(listen(sock_fd,5) == -1){
        perror("listen error");
        close(sock_fd);
        exit(-1);
    }

    return sock_fd;
}

int tcp_accept(int sock_fd){
    int new_fd = 0;
    struct sockaddr_in addr;
    unsigned int addrlen = sizeof(struct sockaddr);

    memset(&addr,0, sizeof(struct sockaddr_in));

    new_fd = accept(sock_fd, (struct sockaddr *)&addr, &addrlen);
    //new_fd = accept(sock_fd, NULL, NULL);//不考虑请求方的信息
    if(new_fd == -1){
        perror("accept error");
        close(sock_fd);
        exit(-1);
    }
    //打印请求者的地址和端口
    printf("%s %d connect\n", inet_ntoa(addr.sin_addr),
           ntohs(addr.sin_port));
    return new_fd;
}

void tcp_server_chat(int new_fd){
    char buf[2048];
    unsigned int ret;

    // 循环接收,接收一条聊天信息
    memset(buf, 0, sizeof(buf));
    ret = recv(new_fd, buf, sizeof(buf) - 1, 0);
    while (ret) {
        if (ret < 0) {
            perror("recv error");
            break;
        }
        buf[ret] = 0;           // 由于是消息,所以对空间做尾部处理
        printf("receive msg is %s.\n", buf);

      /* //服务器也可以发消息给客户端
        ret = send(new_fd, "hello", 5, 0);
        if (ret < 0) {
            perror("send");
            break;
        }
        */
        
        // 再次等待接收客户端的消息
        memset(buf, 0, sizeof(buf));
        ret = recv(new_fd, buf, sizeof(buf) - 1, 0);
    }
    printf("client close!\n");
    close(new_fd);
}

int tcp_client_init(const char *ip,int port){
    int sock_fd = socket(AF_INET,SOCK_STREAM,0);
    if(sock_fd == -1){
        perror("socket error");
        exit(-1);
    }

    int ret = 0;
    struct sockaddr_in addr;
    memset(&addr,0, sizeof(struct sockaddr_in));

    addr.sin_family = AF_INET;//ipv4
    addr.sin_port = htons(port);//主机端口
    addr.sin_addr.s_addr = inet_addr(ip);//主机地址

    ret = connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr));
    if(ret == -1){
        perror("connect error");
        close(sock_fd);
        exit(-1) ;
    }

    return sock_fd;
}

void tcp_client_chat(int sock_fd){
    char buf[1024];
    unsigned int ret;

    printf("Client:");
    while (fgets(buf, sizeof(buf), stdin)) {

        // 发送字符串,不发送'\0'数据
        ret = send(sock_fd, buf, strlen(buf) - 1, 0);
        if (ret < 0) {
            perror("send");
            break;
        }
        printf("send %d bytes success!\n", ret);
        printf("Client:");
    }
}

server.c

#include "tcp_fun.h"

#define IP "192.168.200.134"//本机地址
#define PORT 2233
int main() {
    int sock_fd = tcp_server_init(IP,PORT);
    int new_fd;

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

    while (1) {
        new_fd = tcp_accept(sock_fd);
        if (new_fd < 0)
            break;
        tcp_server_chat(new_fd);
    }
}

client.c

#include "tcp_fun.h"

#define IP "192.168.200.134"
#define PORT 2233
int main() {
    int sock_fd = tcp_client_init(IP,PORT);

    tcp_client_chat(sock_fd);
}

Makefile

all:
	gcc -o client client.c tcp_fun.c
	gcc -o server server.c tcp_fun.c

在这里插入图片描述

10、udp通信实例

server.c

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

#define IP "192.168.200.134"
#define PORT 2233

int main() {
    int fd,ret;
    unsigned int len;
    char buf[1024] = {0};

    fd = socket(AF_INET,SOCK_DGRAM,0);
    if(fd < 0){
        perror("socket");
        return -1;
    }

    struct sockaddr_in addr;
    memset(&addr,0, sizeof(struct sockaddr));

    //公布自己的地址和端口
    addr.sin_port = htons(PORT);
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(IP);

    ret = bind(fd,(struct sockaddr *)&addr, sizeof(struct sockaddr));
    if(ret < 0){
        perror("bind");
        return -1;
    }


    printf("host :ip = %s  port = %d\n",IP,PORT);

    struct sockaddr_in c_addr;//暂存客户端的地址
    len = sizeof(struct sockaddr);
    memset(&c_addr,0, len);

    ret = (int)recvfrom(fd,buf,1024-1,0,(struct sockaddr *)&c_addr,&len);
    while (ret>0){

        //接收消息并回复
        printf("recv %d bytes from %s:%d msg is: %s",ret,
               inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port),buf);
        sendto(fd,"msg recv",9,0,(struct sockaddr *)&c_addr,len);

        //重复接收
        memset(&c_addr,0, len);
        ret = (int)recvfrom(fd,buf,1024-1,0,(struct sockaddr *)&c_addr,&len);
    }
    return 0;
}

client.c

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


int main(int argc ,char **argv) {
    int fd,ret,id;
    unsigned int len;
    char buf[1024];

    if(argc < 3){
        printf("参数过少\n");
        return 0;
    }

    fd = socket(AF_INET,SOCK_DGRAM,0);
    if(fd < 0){
        perror("socket");
        return -1;
    }

    struct sockaddr_in s_addr;
    len =  sizeof(struct sockaddr);
    memset(&s_addr,0, len);

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

    ret = 1;
    id = fork();
    if(id == 0){
        //子进程发消息
        while (ret > 0){
            fgets(buf, sizeof(buf),stdin);
            ret = (int)sendto(fd,buf, strlen(buf),0,(struct sockaddr *)&s_addr,len);
        }
    } else{
        //父进程收消息(就不做出错判断)
        ret = (int)recvfrom(fd,buf,1024-1,0,(struct sockaddr *)&s_addr,&len);
        while (ret>0){
            //接收消息
            printf("recv %d bytes from %s:%d msg is: %s\n",ret,
                   inet_ntoa(s_addr.sin_addr), ntohs(s_addr.sin_port),buf);

            //重复接收
            memset(&s_addr,0, len);
            ret = (int)recvfrom(fd,buf,1024-1,0,(struct sockaddr *)&s_addr,&len);
        }
        wait(NULL);
    }

    return 0;
}

在这里插入图片描述> 参考:man手册、《高质量嵌入式linuxc编程 第2版》355-363

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值