Linux C 下的socket网络编程

1.socket简介

        在我们常用的网络通信中,socket是最常用的一种,socket编程也称套接字编程,下面我们将对socket编程进行相关讲解,其中包括服务器与客户端通信讲解,以及代码实现。

        Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

2.socket网络通信步骤

在我们利用socket创建网络通信连接的时候,遵循下图所示的步骤:

图  1
图  2

如图1所示,当我们两台PC进行通信时,一方的服务器端server向另一方的客户端client发送数据时,由socket创建网络通道,但必须指定双方所使用的协议种类及端口号,同时还需告知IP地址。

其中IP地址相当于住址,用于查找PC所在的位置,端口号相当于“房间号”,用与告知通信双方所使用的端口,常用的通信协议由TCP/UDP两种,下面将补充介绍:

TCP/UDP协议对比:

1.TCP是面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。

2.TCP提供可靠的服务。也就是说TCP连接传送的数据无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。 

3.TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低。

4.每一条TCP连接只能是点到点的;UDP支持一对一一对多多对一多对多的交互式通信

5.TCP首部开销20字节;UDP的首部开销小,只有8字节。

6.TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。

如图2所示,在服务器端,首先新建一个socket套接字,同时绑定地址及端口号,这时进入监听状态,客户端此时若想连接服务器端,则发出连接请求,待服务器端接收请求后,一条完整的通信方式就建立完成,客户端和服务器便可进行数据交换了。

3.相关API介绍

创建套接字int socket(int domain,int type,int protocol),其中domain指明所使用的协议族,通常使用的因特网域为IPv4,设置为AF_INET。type为参数指定socket的类型,我们使用的为TCP协议,可以保证数据传输的正确性与顺序性,因此第二个参数设置为SOCK_STREAM。potocol通常赋值为“0”,0选择type类型对应的默认协议。返回值:若成功则返回socket套接字的文件描述符,若失败则返回-1.

IP与端口号绑定int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen),参数说明:sockfd为socket返回的描述符,addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针。其中第二个参数addr给出的结构体如下:

            struct sockaddr {
               sa_family_t sa_family;
               char        sa_data[14];
           }

结构体中的成员,sa_data[ ]表示进程地址;而bind函数中的第三个参数addrlen表示参数addr的长度;addr参数可以接受多种类型的结构体,而这些结构体的长度各不相同,因此需要使用addrlen参数额外指定结构体长度;经常为了传输IPv4的地址及端口号,定义一个struct sockaddr_in类型的结构体,但最后使用bind函数时必须转换成struct sockaddr *类型的。struct sockaddr_in如下:

struct sockaddr_in{
    sa_family_t sin_family;//协议族
    in_port_t sin_port;//端口号
    struct in_addr sin_addr;//IP地址结构体
    unsigned char sin_zero[8]://填充,没有实际意义
}

注意,在本结构体的第三个成员,相当于结构体的成员为结构体,注意结构体成员的引用。

地址转换int inet_aton(const char *straddr,struct in_addr *addrp);该函数的作用是将字符串形式的IP地址如“192.168.0.0.1”转换成网络能识别的形式,第一个参数straddr为IP地址字符串,第二个参数为struct in_addr *型数据,因此保持类型一致,我们首先给struct sockaddr_in中的第三个成员结构体给出IP地址,此时的类型刚好为struct in_addr类型,但要求的是结构体指针,我们只需进行取地址运算即可。

cha *inet_ntoa(struct in_addr inaddr)该函数的作用是将网络格式的IP地址转换成字符串格式,通常用于接收方接收对方的IP地址并转换为字符串形式。

监听int listen(int sockfd,int backlog);该函数只用于服务器端不断等待客户端的连接请求,第一个参数为描述符,第二个参数backlog指定在请求队列中允许的最大请求数。

接收连接int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);该函数用于接受客户端请求的连接。第一个参数为描述符,第二个参数addr用来返回已连接的客户端的协议地址,第三个参数addrlen为客户端地址长度。

请求连接int connect(int sockfd,const struct sockaddr *addr,socklen_t *addrlen)该函数用于客户端请求连接服务器端的函数,sockfd为描述符,addr为服务器端的IP地址和端口号的地址结构指针,addrlen为地址长度。

服务器端代码如下:

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

int main(int argc,char **argv)
{
        int s_fd;
        int c_fd;
        int n_read;
        char readBuf[128];
        char msg[128]={0};

        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;
        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));
        //1.socket
        s_fd=socket(AF_INET,SOCK_STREAM,0);
        if(s_fd==-1){
                perror("socket");
                exit(-1);
        }
       //2.bind
        s_addr.sin_family=AF_INET;
        s_addr.sin_port=htons(atoi(argv[2]));
        inet_aton(argv[1],&s_addr.sin_addr);
        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
        //3.listen
        listen(s_fd,10);
        //4.accept
        int c_len=sizeof(struct sockaddr_in);
        while(1){
                c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&c_len);

                if(c_fd==-1){
                        perror("accept");
                }
                printf("get connect :%s\n",inet_ntoa(c_addr.sin_addr));
                //5.read
                if(fork() == 0){
                        if(fork()==0){
                                while(1){
                                        memset(msg,0,sizeof(msg));
                                        printf("input \n:");
                                        gets(msg);
                                                                             
                                        write(c_fd,msg,strlen(msg));
                                        sleep(1);
                                }
                        }
                                while(1){
                                        memset(readBuf,0,sizeof(readBuf));
                                        n_read=read(c_fd,readBuf,128);
                                        if(n_read==-1){
                                        perror("read");
                                        }
                                        else{
                                        printf("get message %d:%s\n ",n_read,readBuf);
                                        }
                                }
                        }
                }
        return 0;
}
                
  

在服务器端,当我们与客户端连接成功后,我们开辟一个子进程用来不断地读取客户端发送过来的消息并打印,与此同时再开辟一个子进程用来检测用户的输入,实现可以将服务器端的消息发送给客户端。

客户端代码:

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

int main(int argc,char **argv)
{
        int c_fd;
        int n_read;
        char readBuf[128];
        char msg[128]={0};
        struct sockaddr_in c_addr;
        memset(&c_addr,0,sizeof(struct sockaddr_in));
        //1.socket
        c_fd=socket(AF_INET,SOCK_STREAM,0);
        if(c_fd==-1){
                perror("socket");
                exit(-1);
        }
        //2.connect
        c_addr.sin_family=AF_INET;
        c_addr.sin_port=htons(atoi(argv[2]));
        inet_aton(argv[1],&c_addr.sin_addr);
        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr))==-1){
                perror("connect");
                exit(-1);
        }
        printf("get connect :%s\n",inet_ntoa(c_addr.sin_addr));
        while(1){
                //3.send
                if(fork()==0){
                        while(1){
                                memset(msg,0,sizeof(msg));
                                printf("input \n:");
                                gets(msg);
                                write(c_fd,msg,strlen(msg));
                                sleep(2);
                        }
                }
               //4.read
                while(1){
                        memset(readBuf,0,sizeof(readBuf));
                        n_read=read(c_fd,readBuf,128);
                        if(n_read==-1){
                                perror("read");
                        }
                        else{
                                printf("get message %d:%s\n ",n_read,readBuf);
                        }
                }
        }
        return 0;
}

与服务器端不同,当我们请求连接成功后,便开辟子进程与服务器间进行通信。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux下进行网络编程可以使用C语言提供的一些系统调用和库函数来实现。下面是一些常用的函数和步骤: 1. 创建套接字(Socket):使用`socket()`函数创建一个套接字,指定协议族(如AF_INET)、套接字类型(如SOCK_STREAM)和协议(如0表示自动选择)。 2. 绑定地址:使用`bind()`函数将套接字与本地地址绑定,指定IP地址和端口号。 3. 监听连接请求(对于服务器):使用`listen()`函数将套接字设置为监听模式,指定最大连接数。 4. 接受连接请求(对于服务器):使用`accept()`函数接受客户端的连接请求,返回一个新的套接字,用于与客户端进行通信。 5. 连接到服务器(对于客户端):使用`connect()`函数连接到服务器,指定服务器的IP地址和端口号。 6. 发送和接收数据:使用`send()`和`recv()`函数发送和接收数据,可以使用循环来确保完整的发送和接收。 7. 关闭套接字:使用`close()`函数关闭套接字,释放资源。 下面是一个简单的示例代码,演示了一个简单的服务器和客户端的通信: 服务器端代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 8080 #define MAX_BUFFER_SIZE 1024 int main() { int server_fd, new_socket; struct sockaddr_in address; int addrlen = sizeof(address); char buffer[MAX_BUFFER_SIZE] = {0}; char *message = "Hello from server!"; // 创建套接字 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons( PORT ); // 绑定地址 if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) { perror("bind failed"); exit(EXIT_FAILURE); } // 监听连接请求 if (listen(server_fd, 3) < 0) { perror("listen failed"); exit(EXIT_FAILURE); } // 接受连接请求 if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) { perror("accept failed"); exit(EXIT_FAILURE); } // 发送消息给客户端 send(new_socket, message, strlen(message), 0); printf("Hello message sent\n"); // 接收客户端的消息 read(new_socket, buffer, MAX_BUFFER_SIZE); printf("Client says: %s\n", buffer); // 关闭套接字 close(new_socket); close(server_fd); return 0; } ``` 客户端代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8080 #define MAX_BUFFER_SIZE 1024 int main() { int sock = 0; struct sockaddr_in serv_addr; char buffer[MAX_BUFFER_SIZE] = {0}; char *message = "Hello from client!"; // 创建套接字 if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); // 转换IP地址 if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) { perror("invalid address/ Address not supported"); exit(EXIT_FAILURE); } // 连接到服务器 if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { perror("connection failed"); exit(EXIT_FAILURE); } // 发送消息给服务器 send(sock, message, strlen(message), 0); printf("Hello message sent\n"); // 接收服务器的消息 read(sock, buffer, MAX_BUFFER_SIZE); printf("Server says: %s\n", buffer); // 关闭套接字 close(sock); return 0; } ``` 这是一个简单的例子,仅用于展示网络编程的基本概念。在实际应用中,你可能需要处理更多的错误情况、添加更多的功能,并考虑网络传输的安全性和性能等因素。你可以根据具体需求调整代码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值