C语言实现一个hello/hi的简单聊天程序并跟踪分析到系统调用

socket编程介绍

Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,可以用它们来开发TCP/IP网络上的应用程序。

Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的 Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。

常用的Socket类型有两种:流式Socket (SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。

client/server通信模型

在客户/服务器模式中我们将请求服务的一方称为客户(client),将提供某种服务的一方称为服务器(server)。

一个服务程序通常在一个众所周知的地址监听对服务的请求,也就是说服务进程一直处于休眠状态,直到一个客户对这个服务的地址提出了连接请求。在这个时刻,服务程序被“唤醒”并且为客户提供服务—对客户的请求作出适当的反应。

套接字

套接字是一个通信终结点,它是Socket应用程序用来在网络上发送或接收数据包的对象。

套接字具有类型,与正在运行的进程相关联,并且可以有名称。

套接字一般只与使用网际协议组的同一“通信域”中的其他套接字交换数据。

流式套接字编程流程

客户端

1.创建套接字

client_sockfd=socket(PF_INET,SOCK_STREAM,0)

用socket函数创建套接字,返回套接字描述符

2.定义服务器端地址

struct sockaddr_in remote_addr; //服务器端网络地址结构体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); //服务器端口

3.与服务器建立连接

connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr)

用于tcp/ip应用的地址结构类型是socketsddr_in,而套接字socket中应用的地址类型为socketaddr,所以需要进行强制类型转换。

4.发送接收数据

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

发送数据时已知数据的长度,用strlen(buf)计算;而接收数据时不知道接收数据的长度,就必须将接收长度设为定义的数组的最大值,再通过接收函数的返回值来确定数组的实际大小。

5.关闭套接字

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

没有数据可发送就关闭套接字

服务器端

1.创建套接字,定义自身的地址

int server_sockfd=socket(PF_INET,SOCK_STREAM,0);//服务器端套接字struct sockaddr_in my_addr;   //服务器网络地址结构体
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); //服务器端口号

2.将套接字与地址绑定

bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr)

3.监听是否有客户请求

listen(server_sockfd,5);

4接受客户端请求

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

accept函数返回新的连接的套接字描述符。为每个新的连接请求创建了一个新的套接字,服务器只对新的连接使用该套接字,原来的监听套接字接受其他的连接请求。新的连接上传输数据使用新的套接字,使用完毕,服务器将关闭这个套接字。

5.接收并发送数据

while((len=recv(client_sockfd,buf,BUFSIZ,0))>0)
    {
        buf[len]=‘\0‘;
        printf("recieved:%s\n",buf);
                printf("Enter string to send: ");
                scanf("%s",buf);
        if(send(client_sockfd,buf,strlen(buf),0)<0)
        {
            perror("write");
            return 1;
        }
    }

6关闭套接字

close(client_sockfd);
close(server_sockfd);

实现代码

客户端

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <sys/types.h>
 4 #include <sys/socket.h>
 5 #include <netinet/in.h>
 6 #include <arpa/inet.h>
 7 #include <unistd.h>
 8
 9 int main(int argc, char *argv[])
10 {
11     int client_sockfd;
12     int len;
13     struct sockaddr_in remote_addr; //服务器端网络地址结构体
14     char buf[BUFSIZ];  //数据传送的缓冲区
15     memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零
16     remote_addr.sin_family=AF_INET; //设置为IP通信
17     remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址
18     remote_addr.sin_port=htons(8000); //服务器端口号
19
20     /*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/
21     if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)
22     {
23         perror("socket");
24         return 1;
25     }
26
27     /*将套接字绑定到服务器的网络地址上*/
28     if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0)
29     {
30         perror("connect");
31         return 1;
32     }
33     printf("connected to server\n");
34     len=recv(client_sockfd,buf,BUFSIZ,0);//接收服务器端信息
35          buf[len]=‘\0‘;
36     printf("%s",buf); //打印服务器端信息
37
38     /*循环的发送接收信息并打印接收信息--recv返回接收到的字节数,send返回发送的字节数*/
39     while(1)
40     {
41         printf("Enter string to send:");
42         scanf("%s",buf);
43         if(!strcmp(buf,"quit"))
44             break;
45         len=send(client_sockfd,buf,strlen(buf),0);
46         len=recv(client_sockfd,buf,BUFSIZ,0);
47         buf[len]=‘\0‘;
48         printf("received:%s\n",buf);
49     }
50     close(client_sockfd);//关闭套接字
51     return 0;
52 }

服务器端

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <sys/types.h>
 4 #include <sys/socket.h>
 5 #include <netinet/in.h>
 6 #include <arpa/inet.h>
 7 #include <unistd.h>
 8
 9 int main(int argc, char *argv[])
10 {
11     int server_sockfd;//服务器端套接字
12     int client_sockfd;//客户端套接字
13     int len;
14     struct sockaddr_in my_addr;   //服务器网络地址结构体
15     struct sockaddr_in remote_addr; //客户端网络地址结构体
16     int sin_size;
17     char buf[BUFSIZ];  //数据传送的缓冲区
18     memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零
19     my_addr.sin_family=AF_INET; //设置为IP通信
20     my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上
21     my_addr.sin_port=htons(8000); //服务器端口号
22
23     /*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/
24     if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)
25     {
26         perror("socket");
27         return 1;
28     }
29
30         /*将套接字绑定到服务器的网络地址上*/
31     if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)
32     {
33         perror("bind");
34         return 1;
35     }
36
37     /*监听连接请求--监听队列长度为5*/
38     listen(server_sockfd,5);
39
40     sin_size=sizeof(struct sockaddr_in);
41
42     /*等待客户端连接请求到达*/
43     if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0)
44     {
45         perror("accept");
46         return 1;
47     }
48     printf("accept client %s\n",inet_ntoa(remote_addr.sin_addr));
49     len=send(client_sockfd,"Welcome to my server\n",21,0);//发送欢迎信息
50
51     /*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/
52     while((len=recv(client_sockfd,buf,BUFSIZ,0))>0)
53     {
54         buf[len]=‘\0‘;
55         printf("recieved:%s\n",buf);
56                 printf("Enter string to send: ");
57                 scanf("%s",buf);
58         if(send(client_sockfd,buf,strlen(buf),0)<0)
59         {
60             perror("write");
61             return 1;
62         }
63     }
64     close(client_sockfd);
65     close(server_sockfd);
66     return 0;
67 }

运行结果

跟踪分析到系统调用

当用户进程使用socket API 的时候,会产生向量为0x80的编程异常,系统执行系统调用。

进程传递系统调用号到寄存器eax,指明需要哪个系统调用,同时会将系统调用需要的参数存入相关寄存器。

系统调用处理函数system_call是Linux中所有系统调用的入口点,通过进程存在eax寄存器中的系统调用号决定调用哪个系统调用,而所有的socket系统调用的总入口时sys_socketcall()。

对如下代码跟踪分析

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

用户进程将sys_socket()的系统调用号198存入eax中,并将socket()函数中的三个参数分别存入ebx,ecx,edx寄存器,产生一个0x80的编程异常。系统调用处理函数根据系统调用号,执行sys_socket()。

sys_socket在内核中的源码为:

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
    int retval;
    struct socket *sock;
    int flags;

...
    retval = sock_create(family, type, protocol, &sock);
    if (retval < 0)
        goto out;

    retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
    if (retval < 0)
        goto out_release;

out:
    /* It may be already another descriptor 8) Not kernel problem. */
    return retval;

out_release:
    sock_release(sock);
    return retval;
}

可以看到,socket函数主要由sock_createsock_map_fd这两个函数完成。

sock_create函数用于创建socket。sock_create() 实际调用的是 __sock_create(),其中比较重要的是sock_alloc()pf->create()两个函数。

sock_map_fd函数用于得到一个文件号。这个函数主要有两个部分,一个是创建file文件结构,fd文件描述符,另一部分是将file文件结构和fd文件描述符关联,同时将上一步返回的socket也一起绑定,形成一个完整的逻辑。

socket系统调用的操作概述为:首先在内核生成一个socket_alloc 和tcp_sock类型的对象,其中sock_alloc对象中的socket和tcp_sock对象的sock绑定,sock_alloc对象中的inode和file类型对象绑定。然后将分配的文件描述符fd和file对象关联,最后将这个文件描述符fd返回给用户使用。

原文地址:https://www.cnblogs.com/qfdzztt/p/12012056.html

时间: 2024-10-16 03:35:59

C语言实现一个hello/hi的简单聊天程序并跟踪分析到系统调用的相关文章

Linux下C语言多线程,网络通信简单聊天程序

原文:Linux下C语言多线程,网络通信简单聊天程序 功能描述:程序应用多线程技术,可是实现1对N进行网络通信聊天.但至今没想出合适的退出机制,除了用Ctr+C.出于演示目的,这里采用UNIX域协议(文件系统套接字),程序分为客户端和服务端.应用select函数来实现异步的读写操作. 先说一下服务端:首先先创建套接字,然后绑定,接下进入一个无限循环,用accept函数,接受“连接”请求,然后调用创建线程函数,创造新的线程,进入下一个循环.这样每当有一个新的“连接”被接受都会创建一个新的线程,实现

利用命名管道实现进程间的通信(简单聊天程序的实现)

管道的本质是一种文件,通常是指把一个进程的输出直接传递给另一个进程的输入. 命名管道(named pipe)是一种特殊的文件类型(FIFO文件),它在文件系统中以文件名的形式存在. 下面是Linux中命名管道的文件格式: 通过命令行创建命名管道可以通过mkfifo命令,函数调用如下所示: 1 #include <sys/types.h> 2 #include <sys/stat.h> 3 4 int mkfifo(const char *filename,mode_t mode);

《Java项目实践》:简单聊天程序

<Java项目实践>:简单聊天程序 由于这个简单程序,还是涉及到很多的知识点,下面我们就一点一点的来完成. 我们熟悉的QQ聊天,就有一个界面,是吧,我们自己做一个简单的聊天程序,因此我们也就需要为Client写一个界面.因此,这就是我们第一步需要完成的任务. 第一步:为Client端写一个界面 完成一个界面有两种方法,一种是使用Frame对象来完成,另外一种是继承JFrame类来完成.本项目使用第二种. 第二种继承JFrame类完成的界面的程序如下: public class ChatClie

基于html5 localStorage , web SQL, websocket的简单聊天程序

new function() { var ws = null; var connected = false; var serverUrl; var connectionStatus; var sendMessage; var connectButton; var disconnectButton; var sendButton; var open = function() { var url = serverUrl.val(); ws = new WebSocket(url); ws.onope

用R语言实现一个求导的简单例子

在学习导数的时候,老师都会这样解释,假设y=f(x),对点(x0,y0)的求导过程如下:设dx是一个很小的数=> y0+dy=f(x0+dx)=> dy=f(x0+dx)-y0则在这一点的导数a=dy/dx这样假设的前提是dx很小,所以x0至(x0+dx)很短,可以近似为一条直线,则dy/dx可以看成是点(x0,y0)和点(x0+dx,y0+dy)连成直线的斜率因此关于某个点的导数,其意义也可以解释成在该点的变化率 下面用R语言实现一个简单例子:假设:y=1/x,求点(1,1)的导数 x<

websocket实现简单聊天程序

程序的流程图: 主要代码: 服务端 app.js 先加载所需要的通信模块: var express = require('express'); var app = express(); var http = require('http').createServer(app); var io = require('socket.io').listen(http); var fs = require('fs'); 创建用户列表和消息列表: var person = []; var history =

Android 网络编程基础之简单聊天程序

前一篇讲了Android的网络编程基础,今天写了一个简单的聊天程序分享一下 首先是服务端代码: 1 package com.jiao.socketdemo; 2 3 import java.io.BufferedReader; 4 import java.io.BufferedWriter; 5 import java.io.IOException; 6 import java.io.InputStreamReader; 7 import java.io.OutputStreamWriter;

Tcpclient简单聊天程序

以前C/S通讯这一块都是基于套接字(Sockets)编程,但是面对业务逻辑复杂的C/S系统,通讯这一块往往都是被封装好了的,前几天写的一个小小窗体聊天程序,主要是练习一下C#封装的TcpListener.TcpClient以及NetworkStream的使用,直接看图上代码: [csharp] view plaincopyprint? using System; using System.Drawing; using System.Collections; using System.Compon

从语言学习实例第二天(简单小程序)

计算三角形面积 没有考虑构不成三角形 考虑完全 另外自我解惑 %f是用于格式化输入输出函数,对应类型为float的格式字符.加数字的情况仅适用于输出函数,比如printf. 其形式为 printf("%a.bf", var); 其中a,b为常数. 含义为: 1 a, 代表输出占|a|(a的绝对值)个字节的宽度,当实际宽度超过a时,按实际宽度输出,否则输出a个字节,不足部分补空格.  如果a为正数,输出的有效数字在右侧,左侧补空格. 如果a为负数,输出的有效数字在左侧,右侧补空格. 2