以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#网络编 ...
随机推荐
- react 实现圆环进度条
import React, { useState, useEffect } from "react" import { css } from "emotion" ...
- 忘记本地MySQL数据库密码的解决方法
平台:win7 1.打开cmd窗口,进入 MySQL的安装目录. 2.停止MySQL的服务.已经停止了. 右键MySQL,看到启动是可点击的,证明MySQL服务已停止运行. 3.将root用户对数据库 ...
- 在多租户(容器)数据库中如何创建PDB:方法6 DBCA本地克隆PDB
基于版本:19c (12.2.0.3) AskScuti 创建方法:DBCA静默本地克隆PDB.根据 CDB1 中的 PDB1 克隆出 CDB1 中的 PDB_CLONE 对应路径:Creating ...
- JS高级---函数的几个成员
函数的几个成员 函数中有一个name属性----->函数的名字, name属性是只读的, 不能修改 函数中有一个arguments属性--->实参的个数 函数中有一个length属性--- ...
- CentOS之service iptables stop 显示not loaded
停止firewalld服务停止防火墙,并禁用这个服务 sudo systemctl stop firewalld.servicesudo systemctl disable firewalld.ser ...
- HBase 中读 HDFS 调优
HDFS Read调优 在基于 HDFS 存储的 HBase 中,主要有两种调优方式: 绕过RPC的选项,称为short circuit reads 开启让HDFS推测性地从多个datanode读数据 ...
- Strange Bank(找零问题)
题目描述 为了使取钱变得困难,某家银行在一次操作中只允许其客户提取下列金额之一: 1日元(日本的货币) 6日元,62(=36)日元,63(=216)日元,… 9日元,92(=81)日元,93(=729 ...
- 【C语言】一堆数组中存放了10个小于100的整数,请编程对所有数据按照从小到大的顺序进行排序,若个位数相等,则按照十位从小到大的顺序排序,输出排序后的结果
分析:取余,判断个位是否相等,利用冒泡法排序 #include <stdio.h> int main() { ] = { ,,,,,,,,, };/*数组*/ int i, j, k; ; ...
- Bridge(Ad Hoc)
- python自动化用例框架搭建--目录结构规划
目录结构搭建 Test_framework |--config(配置文件) |--data(数据文件) |--drivers(驱动) |--log(日志) |--report(测试报告) |--tes ...