以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析

套接字是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

端口号:

端口号(port)是传输层协议的内容。

端口号是一个2字节16位的整数;

端口号用来标识一个进程,告诉操作系统,当前这个数据交给哪一个程序进行解析;

IP地址 + 端口号能标识网络上的某一台主机的某一个进程;

一个端口号只能被一个进程占用。

端口号 & 进程:

概念

进程有唯一的pid标识,端口号也能标识进程;

一个进程可以绑定多个端口号,一个端口号不能被多个进程绑定。

源端口号 & 目的端口号

传输层协议(TCP/IP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述“数据是谁的?发给谁?”

TCP:

(TCP)传输控制协议,面向连接。是一种提供可靠数据传输的通用协议。

传输层协议

有连接

可靠传输

面向字节流

socket API:

1.创建socket文件描述符  (TCP/UDP,客户端+服务器)

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

参数1(domain): 选择创建的套接字所用的协议族;

AF_INET : IPv4协议;

AF_INET6: IPv6协议;

AF_LOCAL: Unix域协议;

AF_ROUTE:路由套接口;

AF_KEY :密钥套接口。

参数2(type):指定套接口类型,所选类型有:

SOCK_STREAM:字节流套接字;

SOCK_DGRAM : 数据报套接字;

SOCK_RAW : 原始套接口。

procotol: 使用的特定协议,一般使用默认协议(NULL)。

2.绑定端口号  (TCP/IP,服务器)

int bind(int socket, const struct sockaddr *address, socklen_t address_len);

参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

参数2(address):指向特定协议的地址指针。

参数3(address_len):上面地址结构的长度。

返回值:没有错误,bind()返回0,否则SOCKET_ERROR。

3.开始监听socket  (TCP,服务器)

int listen(int socket, int backlog);

参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

参数2(backlog):所监听的端口队列大小。

4.接受请求  (TCP,服务器)

int accept(int socket, struct sockaddr* address, socklen_t* address_len);

参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

参数2(address):指向特定协议的地址指针。

参数3(addrlen):上面地址结构的长度。

返回值:没有错误,bind()返回0,否则SOCKET_ERROR。

5.建立连接  (TCP,客户端)

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

6.关闭套接字

int close(int fd);

参数(fd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

服务器端程序:

1 、加载套接字库

2 、创建套接字(socket )。

3 、将套接字绑定到一个本地地址和端口上(bind )。

4 、将套接字设为监听模式,准备接收客户请求(listen )。

5 、等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept )。

6 、用返回的套接字和客户端进行通信(send/recv )。

7 、返回,等待另一客户请求。

8 、关闭套接字。

#include <stdio.h>

#include <string.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <unistd.h>

int main(int argc, char *argv[])

{

       int server_sockfd;//服务器端套接字

       int client_sockfd;//客户端套接字

       int len;

       struct sockaddr_in my_addr;   //服务器网络地址结构体

       struct sockaddr_in remote_addr; //客户端网络地址结构体

       int sin_size;

       char buf[BUFSIZ];  //数据传送的缓冲区

       memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零

       my_addr.sin_family=AF_INET; //设置为IP通信

       my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上

       my_addr.sin_port=htons(8000); //服务器端口号

       /*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/

       if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)

       { 

              perror("socket");

              return 1;

       }

        /*将套接字绑定到服务器的网络地址上*/

       if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)

       {

              perror("bind");

              return 1;

       }

       /*监听连接请求--监听队列长度为5*/

       listen(server_sockfd,5);

       sin_size=sizeof(struct sockaddr_in);

       /*等待客户端连接请求到达*/

       if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0)

       {

              perror("accept");

              return 1;

       }

       printf("accept client %s\n",inet_ntoa(remote_addr.sin_addr));

       len=send(client_sockfd,"Welcome to my server\n",21,0);//发送欢迎信息

       /*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/

       while((len=recv(client_sockfd,buf,BUFSIZ,0))>0)

       {

              buf[len]='\0';

              printf("%s\n",buf);

              if(send(client_sockfd,buf,len,0)<0)

              {

                     perror("write");

                     return 1;

              }

       }

       close(client_sockfd);

       close(server_sockfd);

       return 0;

}

  

客户端程序:

1 、加载套接字库

2 、创建套接字(socket )。

3 、向服务器发出连接请求(connect )。

4 、和服务器端进行通信(send/recv )。

5 、关闭套接字。

#include <stdio.h>

#include <string.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <unistd.h>

int main(int argc, char *argv[])

{

       int client_sockfd;

       int len;

       struct sockaddr_in remote_addr; //服务器端网络地址结构体

       char buf[BUFSIZ];  //数据传送的缓冲区

       memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零

       remote_addr.sin_family=AF_INET; //设置为IP通信

       remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址

       remote_addr.sin_port=htons(8000); //服务器端口号

       /*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/

       if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)

       {

              perror("socket");

              return 1;

       }

       /*将套接字绑定到服务器的网络地址上*/

       if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0)

       {

              perror("connect");

              return 1;

       }

       printf("connected to server\n");

       len=recv(client_sockfd,buf,BUFSIZ,0);//接收服务器端信息

         buf[len]='\0';

       printf("%s",buf); //打印服务器端信息

       /*循环的发送接收信息并打印接收信息--recv返回接收到的字节数,send返回发送的字节数*/

       while(1)

       {

              printf("Enter string to send:");

              scanf("%s",buf);

              if(!strcmp(buf,"quit"))

                     break;

              len=send(client_sockfd,buf,strlen(buf),0);

              len=recv(client_sockfd,buf,BUFSIZ,0);

              buf[len]='\0';

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

       }

       close(client_sockfd);//关闭套接字

    return 0;

}

  

以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析

以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析

这里总结GDB调试过程以及命令:

gcc server.c -o server_gdb -g

gdb server_gdb

 

命令

命令缩写

命令说明

list

l

显示多行源代码

break

b

设置断点,程序运行到断点的位置会停下来

info

i

描述程序的状态

run

r

开始运行程序

display

disp

跟踪查看某个变量,每次停下来都显示它的值

step

s

执行下一条语句,如果该语句为函数调用,则进入函数执行其中的第一条语句

next

n

执行下一条语句,如果该语句为函数调用,不会进入函数内部执行(即不会一步步地调试函数内部语句)

print

p

打印内部变量值

continue

c

继续程序的运行,直到遇到下一个断点

set var name=v

设置变量的值

start

st

开始执行程序,在main函数的第一条语句前面停下来

file

装入需要调试的程序

kill

k

终止正在调试的程序

watch

监视变量值的变化

backtrace

bt

产看函数调用信息(堆栈)

frame

f

查看栈帧

quit

q

退出GDB环境

下面是socket在linux下系统调用的分析

1.在include/linux/syscalls.h中定义了sys_socket函数的函数原型

asmlinkage long sys_socket(int, int, int);

系统调用函数必须满足:

asmlinkage long sys_##function-name(##args){ ,return ret}

以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析

2.在arch/arm/include/asm,unistd.h中,将sys_socket系统调用和系统调用好关联起来

#define __NR_socket 97

__SYSCALL(__NR_socket, sys_socket)//系统调用号为97

以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析

在unistd.h中,同时给出注释表明,给函数的实现在socket.c中

3.进入socket.c(net/中),发现有这样一个函数实现(或者定义)

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol){ ….}

以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析

肯定这就是asmlinkage long sys_socket(int,int,int)的实现了。为了表明这一切,需要进一步查看宏SYSCALL_DEFINE3的定义

SYSCALL_DEINFE3的定义也在syscalls.h中

#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析

那么:SYSCALL_DEFINE3(socket,int,family,int,type,int,protocal)===SYSCALL_DEFINEX(3,socket,__VA_ARGS__)

由此,我们得到

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol){ ….}

SYSCALL_DEFINEX(3,_socket,__VA_ARGS__)

_SYSCALL_DEFINE(3,_socket,__VA_ARGS__)

asmlinkage long sys_socket(int family,int type,int protocol)

而他的原型正是:asmlinkage long sys_socketintintint

4.

分析了系统是如何定义和实现sys_socket的系统调用。接下来,仔细分析sys_socket是如何实现创socket的。在前面,我们知道inet_family_ops中的create函数为inet_create,也就是说,如果要创建inet型的socket,将由函数inet_create来创建。

先来看看inet_family_ops

static const struct net_proto_family inet_family_ops = {

.family = PF_INET,

.create = inet_create,

.owner  = THIS_MODULE,

};

下面看看sys_socket中的函数调用关系:

sys_socket

|

+--------- sock_create

|                    |

|                    +------- __sock_create

|                                         |

|                                         +------- security_socket_create

|                                         +-------- sock_alloc()

|                                         +--------- rcu_dereference(net_families[family])

|                                         +--------- pf->create(net, sock, protocol, kern)

|                                         +--------- module_put(pf->owner)

|                                         +--------- security_socket_post_create

+---------- sock_map_fd

sys_socket 调用sock_create函数,最终调用rcu_dereference函数来得到相应的net_family_ops,在这里是inet_family_ops,然后调用inet_family_ops结构中的create函数,这里是inet_create函数,来创建socket。sock_map_fd是得到一个文件号。

当使用socket(int,int,int)创建一个socket时,socket会调用sys_socket来完成socket的创建。

上一篇:App测试时,区分客户端或服务器端导致问题产生的方法


下一篇:Python之路,Day8 - Python基础 面向对象高级进阶与socket基础