网络编程:IO通信

概要

学习记录,代码解析
服务器为每个客户端连接请求创建一个线程来进行通信。

优点:逻辑清晰
缺点:线程量较大,很耗内存

一、监听端口

1.建立socket

int socketfd = socket(AF_INET, SOCK_STREAM, 0);

socket函数:

int socket(int domain, int type, int protocol)
domain:套接字要使用的协议簇,协议簇的在“linux/socket.h”里有详细定义,常用的协议簇有:
AF_UNIX(本机通信)
AF_INET(TCP/IP – IPv4)
AF_INET6(TCP/IP – IPv6)
type:套接字类型,常用的类型有:
SOCK_STREAM(TCP流)
SOCK_DGRAM(UDP数据报)
SOCK_RAW(原始套接字)
protocol:协议
协议“protocol”一般设置为“0”,就是说在已经确定套接字使用的协议簇和类型时,这个参数的值就为0。
但是有时候创建初始套接字时,在domain参数未知情况下,即不清楚协议簇和类型时,protocol参数可以用来确定协议的种类。

2.定义地址

struct sockaddr_in server_addr;

server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

struct sockaddr和struct sockaddr_in这两个结构体用来处理网络通信的地址

sockaddr在头文件#include <sys/socket.h>中定义, sockaddr的缺陷是: sa_data把目标地址和端口信息混在一起了
struct sockaddr {  
     sa_family_t	sin_family;		//地址族
     char 			sa_data[14];	//套接字中的目标地址和端口信息               
 };
struct sockaddr_in{
	sa_family_t		sin_family;		//地址簇
	unit16_t		sin_port;		//16位TCP/UDP端口号
	struct in_addr	sin_addr;		//32位IP地址
	char			sin_zero[8];	//不使用
};

struct in_addr{
	In_addr_t		s_addr;			//32位IPv4地址
};
htons, htonl: 作用是将端口号由主机字节序转换为网络字节序的整数值
(host to net short, host to net long)

3.绑定socket和地址

if(bind(socketfd, (struct sockaddr*)&server_addr, sizeof(sever_addr))==-1){
	perror("bind error");
    return -1;
}
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

sockfd: 需要绑定的socket
addr: 存放了服务端用于通信的地址和端口
addrlen: 表示addr结构体的大小

4.监听socket

listen(socketfd, 10);

二、连接处理

5.建立连接,并为每个连接建立一个线程

while(1){
	struct sockaddr_in clientaddr;
	socklen_t len = sizeof(clientaddr);
	int clientfd = accept(socketfd, (struct sockaddr*)&clientaddr, &len);
	
	pthread_t thread_id;
	pthread_create(&thread_id, NULL, client_thread, &clientfd);
}
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//成功返回一个新的socket,用于和客户端通信,失败返回-1

sockfd: 文件描述符,被监听的socket
addr: 	返回链接客户端地址信息,含IP地址和端口号
addrlen: 传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
int pthread_create(
	pthread_t *restrict tidp,   //指向线程标识符的指针,用pthread_t创建
	const pthread_attr_t *restrict attr,  //设置线程属性,默认为NULL
	void *(*start_rtn)(void *), //线程运行函数的起始地址
	void *restrict arg 			//无参数时设为NULL即可。有参数时输入参数的地址。当多于一个参数时应当使用结构体传入。
);

6.线程函数,与客户端进行通信


void* client_thread(void* client){
    int clientfd=*(int*) client;
    while(1){
        char buffer[128];
        int count=recv(clientfd, buffer, 128, 0);
        if(count==0){
            close(clientfd);
            return NULL;
        }
        send(clientfd, buffer, count, 0);
    }
}

recv函数: 阻塞式等待连接的另一端发送数据。如果另一端close掉了会返回0

int recv(SOCKET sockfd, char *buf, int len, int flags);

sockfd: 指定接收端套接字描述符
buf: 接收数据的缓存区
len: buf的长度
flags: 一般置0

服务器端代码

#include <stdio.h>
#include <string.h>

#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>

#include <errno.h>
#include <pthread.h>

void* client_thread(void* client){
    int clientfd=*(int*) client;
    while(1){
        char buffer[128];
        int count=recv(clientfd, buffer, 128, 0);
        if(count==0){
            close(clientfd);
            return NULL;
        }
        send(clientfd, buffer, count, 0);
    }
}

int main(){
    //创建socket,返回文件描述符
    int socketfd = socket(AF_INET, SOCK_STREAM, 0);
    
    //定义地址
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(sockaddr_in));

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if(bind(socketfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr))==-1){
        perror("bind error");
        return -1;
    }

    listen(socketfd, 10);

    while(1){
        struct sockaddr_in clientaddr;
        socklen_t len=sizeof(clientaddr);
        int clientfd = accept(socketfd, (struct sockaddr*)&clientaddr, &len);

        pthread_t thread_id;
        pthread_create(&thread_id, NULL, client_thread, &clientfd);
    }

    
}

客户端代码

#include <stdio.h>
#include <string.h>

#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>

#include <errno.h>
#include <pthread.h>

int main(){
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);

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

    target_addr.sin_family=AF_INET;
    target_addr.sin_port=htons(8888);
    target_addr.sin_addr.s_addr=htonl(INADDR_ANY);

    if(connect(clientfd, (struct sockaddr*)&target_addr, sizeof(target_addr))==-1){
        perror("connect error");
        return -1;
    }

    char message[]="hello this is client";
    send(clientfd, message, sizeof(message), 0);

    char buffer[128];
    memset(buffer, 0, sizeof(buffer));
    
    int ret=recv(clientfd, buffer, 128, 0);
    if(ret>0){
        printf("from server message: %s\n", buffer);
    }

    close(clientfd);

}

参考内容:
https://blog.csdn.net/cfm908826/article/details/109403227
https://blog.csdn.net/will130/article/details/53326740

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值