以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; }
这里总结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 |
执行下一条语句,如果该语句为函数调用,不会进入函数内部执行(即不会一步步地调试函数内部语句) |
|
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}
2.在arch/arm/include/asm,unistd.h中,将sys_socket系统调用和系统调用好关联起来
#define __NR_socket 97
__SYSCALL(__NR_socket, sys_socket)//系统调用号为97
在unistd.h中,同时给出注释表明,给函数的实现在socket.c中
3.进入socket.c(net/中),发现有这样一个函数实现(或者定义)
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol){ ….}
肯定这就是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__)
那么: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_socket(int,int,int)
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的创建。
以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析的更多相关文章
- 以您熟悉的编程语言为例完成一个hello/hi的简单的网络聊天程序
Socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信,应用程序通常通过"套接字"向网络发出 ...
- hello/hi的简单的网络聊天程序
hello/hi的简单的网络聊天程序 0 Linux Socket API Berkeley套接字接口,一个应用程序接口(API),使用一个Internet套接字的概念,使主机间或者一台计算机上的进程 ...
- python实现一个简单的网络聊天程序
一.Linux Socket 1.Linux Socke基本上就是BSD Socket(伯克利套接字) 伯克利套接字的应用编程接口(API)是采用C语言的进程间通信的库,经常用在计算机网络间的通信.B ...
- 用Java实现简单的网络聊天程序
Socket套接字定义: 套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开.读写和关闭等操作.套接字允许应用程序将I/O插入到网络中,并与网络中的其他 ...
- telnet指令研究—以网络聊天程序为例
一.telnet指令 Telnet取名自Telecommunications和Networks的联合缩写,是早期个人计算机上连接到服务器主机的一个网络指令,由于存在安全问题,现在已经很少被使用.在wi ...
- 简单的网络爬虫程序(Web Crawlers)
程序比较简单,但是能体现基本原理. package com.wxisme.webcrawlers; import java.io.*; import java.net.*; /** * Web Cra ...
- 使用Java实现hello/hi的简单网络聊天程序
Socket又称套接字,是基于应用服务与TCP/IP通信之间的一个抽象,它是计算机之间进行通信的一种约定或一种方式.通过socket这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送 ...
- 一个简单的IM聊天程序Pie IM(以后会更新)
这个程序用多线程,实现设备之间的聊天,支持win10通知,欢迎下载 依赖的第三方库 win10toast 代码 将以下代码写入任意.py文件 1 print('Welcome to use Pie I ...
- C#编写简单的聊天程序
这是一篇基于Socket进行网络编程的入门文章,我对于网络编程的学习并不够深入,这篇文章是对于自己知识的一个巩固,同时希望能为初学的朋友提供一点参考.文章大体分为四个部分:程序的分析与设计.C#网络编 ...
随机推荐
- [HDU4609] 3-idiots - 多项式乘法,FFT
题意:有\(n\)个正整数,求随机选取一个3组合,能构成三角形的概率. Solution: 很容易想到构造权值序列,对其卷积得到任取两条边(可重复)总长度为某数时的方案数序列,我们希望将它转化为两条边 ...
- IntelliJ IDEA,酷炫插件系列,提高你的工作效率【plugins】
今天介绍一下IDEA的一些炫酷的插件,IDEA强大的插件库. 不仅能给我们带来一些开发的便捷,还能体 ...
- jvm 结构分析
jvm区域总体分两类,heap区和非heap区.heap区又分: Eden Space(伊甸园).Survivor Space(幸存者区).Tenured Gen(老年代-养老区). 非heap区又分 ...
- 集成Log4Net到自己的Unity工程
需要使用的插件库说明: Loxodon Framework Log4NetVersion: 1.0.0© 2016, Clark Yang=============================== ...
- JS点击显示隐藏内容
JS点击显示隐藏密码 思路:获取元素,判断点击,如果DIV显示就隐藏,如果DIV隐藏就显示出来. 1 if(DIV是显示的){ 2 div.style.display='none'; 3 } 4 el ...
- jdk8-》日期时间及其格式处理类特性
一.JDK8之时间⽇期处理类 核⼼类: LocalDate:不包含具体时间的⽇期. LocalTime:不含⽇期的时间. LocalDateTime:包含了⽇期及时间. LocalDate 常⽤API ...
- selenium的鼠标事件操作
自动化测试过程中,经常会用到鼠标事件,在selenium的action_chains模块的ActionChains定义了鼠标操作的一些事件,要使用ActionChains类中的方法,首先需要对Acti ...
- 励志写一篇有味道的博文------json
之前有更古老的数据交互中间键xml,但是由于比较复杂后来出现了json json :轻量级数据交换格式 json与python数据对比 json python object d ...
- Oracle的表空间、用户和表的区别和联系
Oracle的表空间.用户和表的区别和联系 Oracle数据库是通过表空间来存储实际存在的那些表.索引.视图的, 表空间分类: 临时表空间: 用于存储数据库中单持久性模型对象,如表.索引.视图等, ...
- Linux服务器上实现数据库和图片文件的定时备份
一. 1.首先创建一个目录,用于存放备份的数据 2.在该目录下创建两个子目录一个用于存放数据库的信息,一个用于存放图片资源 3.#数据库的备份 执行下面的命令 mysqldump ...