2019-10-11 17:59:40 Sunshinesmile123 阅读数 990
  • Linux Socket编程实战第1季第1部分

    课程特点: 1、手把手的实际操作过程; 2、引导学员一步步去思考; 3、网络技术方面初级的一步步进入linux socket编程的世界; 本课程是linux socket编程的一小部分,从无名套接口开始, 然后逐步深入,这应该是很多课程所没有的。 以通俗的比照讲清楚一些概念,更多的是如何一步步通过代码去实现,并辅之以一些小的项目来更好的 理解linux socket编程的技巧和方法。

    1908 人正在学习 去看看 熊健

第一步:在本地测试:首先是服务器端的socket代码:

package com.serverSocket.main;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;

import java.net.ServerSocket;
import java.net.Socket;


public class Server {

	public static void main(String[] args) throws IOException {

		run();
	}

	public static void run() throws IOException {
		
		try {
			
			ServerSocket serverSocket = new ServerSocket(“你自己所需要开启的监听端口号”);
			
			Socket client = null;
			boolean flag = true;
			while (flag) {
				//System.out.println("服务器已启动,等待客户端请求。。。。");测试可以开启,实际就不要用了。
			
				client = serverSocket.accept();
				
				new Thread(new EchoThread(client)).start();
				Thread.sleep(500);
			}
			client.close();
			serverSocket.close();
			System.out.println("服务器已关闭。");
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}

class EchoThread implements Runnable {
	private Socket client;
	public EchoThread(Socket client) {
		this.client = client;
	}

	public void run() {

		try {

			BufferedReader in = null;
			in = new BufferedReader(new InputStreamReader(client.getInputStream(), "utf-8"));
			String line = "";
			StringBuffer bt = new StringBuffer();
			Socket sk = new Socket("服务器的IP",你自己所需要开启的监听端口号);
			OutputStream os = sk.getOutputStream();
			System.out.println(sk+":"+os);
			while ((line = in.readLine()) != null) {
				//line 为接收的数据
				System.out.println(line);
			}

			sk.close();
			//System.out.println("sk关闭");
		} catch (IOException e1) {
			// TODO 自动生成的 catch 块
			e1.printStackTrace();
		} catch (Exception e) {
			// TODO: handle exception
			System.out.println("error");
		}

	}




}

 客户端的socket代码:

package com.serverSocket.main;

import java.io.*;
import java.net.*;

public class TCPEchoClient {  
	public static void main(String[] args) throws UnknownHostException, IOException {  
        Socket s = new Socket("IP",port);  
        OutputStream os = s.getOutputStream(); 
        String str="你好";
        os.write(str.getBytes("UTF-8"));  
        os.flush();  
        os.close();  
        s.close();  
    }  

}  

                      测试成功之后就是在服务器上进行部署:

你可以将Java文件打包成jar,也可以直接在服务器上编译运行。

我选择的是直接在服务器上编译运行。

Java文件编译运行在服务器上的操作

1.找一个文件目录,在其中创建两个目录src和bin ,src 下用于存放“.Java”文件,bin下用于存放编译之后的“.class”文件。

mkdir src bin

2.将服务端socket和客户端socket的Java文件放到src目录下。

3.编译java文件,将生成的编译文件存放在刚刚创建的bin目录下,“-d”表示指定生成class文件的位置。

javac ./src/Server.java -d ./bin/

javac ./src/TCPEchoClient.java -d ./bin/

4.运行编译好的.class文件。-cp”表示classpath,后跟路径,创建的是什么就指定哪里,否则会报错。之后再指定包名.类名即可运行。

java -cp /bin com.serverSocket.main.Server

java -cp /bin com.serverSocket.main.TCPEchoClient


部署完之后端口监听应该是没有问题的,不过测试客户端连接一般会有问题。会有连接被拒绝的报错,我也没解决呢,不过应该是防火墙的问题或者是其他连接拦截的问题,因为系统不可能直接就让你连接。如果你们有解决方法可以告诉我哦!

查看监听端口的命令:

netstat -an | grep 你所要查看的监听端口

netstat -anp 查看所有端口

 

2018-07-09 17:46:47 sinat_20184565 阅读数 463
  • Linux Socket编程实战第1季第1部分

    课程特点: 1、手把手的实际操作过程; 2、引导学员一步步去思考; 3、网络技术方面初级的一步步进入linux socket编程的世界; 本课程是linux socket编程的一小部分,从无名套接口开始, 然后逐步深入,这应该是很多课程所没有的。 以通俗的比照讲清楚一些概念,更多的是如何一步步通过代码去实现,并辅之以一些小的项目来更好的 理解linux socket编程的技巧和方法。

    1908 人正在学习 去看看 熊健
当前Linux内核的实现,一个socket监听在一个特定的网络命名空间中,不同的命名空间可有具有相同的socket,即可监听相同的地址端口,这样很好的实现网络隔离虚拟化的功能。但是对于网路设备来说,并不需要如此完全的隔离。比如VPN设备/路由器等,一个ike进程或者quagga进程能监听在所有的命名空间,更利于实现和管理。

这样就要求应用层socket能够接收到所有命名空间的数据包,并且能够感知当前连接的命名空间。


数据包接收

内核默认创建一个网络命名空间(init_net),起初所有的socket都监听在默认的网络命名空间。要能够接收其它命名空间的数据包,需要修改sock查找函数。在一个新的连接请求进来之后,查找监听sock时(__inet_lookup_listener),内核默认仅在接收数据包所在的命名空间查找。修改为在找不到的情况下,去init_net命名空间查找,此时,监听在init_net的socket就能接收到新的连接了。


sk = __inet_lookup_listener(dev_net(dev), hashinfo, saddr, sport, daddr, hnum, dif, flags);
if (!sk) {
    sk = __inet_lookup_listener(&init_net, hashinfo, saddr, sport, daddr, hnum, dif, flags);
}


以上是TCP的sock查找修改,对于UDP可做相同的修改。


数据包发送

sock的查找修改之后已经可以接收到新的连接请求,但是并没有修改sock结构中的sk_net的值,其还是init_net(socket总监听在此命名空间),不能使用其查找路由。如要能正常回复此连接请求(SYN+ACK),我们的sock需要使用接收到数据包的接口所在net_namespace的路由。所以在请求路由时,使用接收命名空间查找:


static inline struct net *sock_net(const struct sock *sk)
{
    return read_pnet(&sk->sk_net);
}
struct dst_entry *inet_csk_route_req(struct sock *sk, struct flowi4 *fl4, const struct request_sock *req)
{
    rt = ip_route_output_flow(skb_real_net, fl4, sk);
}


发送连接建立之后正常的数据包涉及到的也是路由问题,如何告诉内核代码要在哪个命名空间发送?此时需要在创建子sock的时候,把真正的接收命名空间保存在子sock中。在发送时使用。例如ip_queue_xmit函数,查询路由时使用真正的child_sk_real_net去查:


int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)
{
 rt = ip_route_output_ports(child_sk_real_net, fl4, sk, daddr, inet->inet_saddr,
        inet->inet_dport, inet->inet_sport, sk->sk_protocol, RT_CONN_FLAGS(sk), sk->sk_bound_dev_if);
}


方能找到正确的出口路由,正常发送数据包。实现应用层socket监听多个网络命名空间。



2020-03-14 18:27:02 nb_zsy 阅读数 25
  • Linux Socket编程实战第1季第1部分

    课程特点: 1、手把手的实际操作过程; 2、引导学员一步步去思考; 3、网络技术方面初级的一步步进入linux socket编程的世界; 本课程是linux socket编程的一小部分,从无名套接口开始, 然后逐步深入,这应该是很多课程所没有的。 以通俗的比照讲清楚一些概念,更多的是如何一步步通过代码去实现,并辅之以一些小的项目来更好的 理解linux socket编程的技巧和方法。

    1908 人正在学习 去看看 熊健
前提

利用ifconfig看当前主机的ip地址 作为客户端代码执行的参数

server.c
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <errno.h> 
#include <sys/socket.h> 
#include <arpa/inet.h> 
#include <netinet/in.h> 
#include <sys/types.h> 
#include <unistd.h> 
#include <sys/time.h> 
  
#define BUFLEN 1024 
#define PORT 6666
#define LISTNUM 20
  
int main() 
{ 
    int sockfd, newfd; 
    struct sockaddr_in s_addr, c_addr; 
    char buf[BUFLEN]; 
    socklen_t len; 
    unsigned int port, listnum; 
    fd_set rfds; 
    struct timeval tv; 
    int retval,maxfd; 
      
    /*建立socket*/ 
    if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1){ 
        perror("socket"); 
        exit(errno); 
    }else 
        printf("socket create success!\n"); 
    memset(&s_addr,0,sizeof(s_addr)); 
    s_addr.sin_family = AF_INET; 
    s_addr.sin_port = htons(PORT); 
    s_addr.sin_addr.s_addr = htons(INADDR_ANY); 
    
    /*把地址和端口帮定到套接字上*/ 
    if((bind(sockfd, (struct sockaddr*) &s_addr,sizeof(struct sockaddr))) == -1){ 
        perror("bind"); 
        exit(errno); 
    }else 
        printf("bind success!\n"); 
    /*侦听本地端口*/ 
    if(listen(sockfd,listnum) == -1){ 
        perror("listen"); 
        exit(errno); 
    }else 
        printf("the server is listening!\n"); 
    while(1){ 
        printf("*****************聊天开始***************\n"); 
        len = sizeof(struct sockaddr); 
        if((newfd = accept(sockfd,(struct sockaddr*) &c_addr, &len)) == -1){ 
            perror("accept"); 
            exit(errno); 
        }else 
            printf("正在与您聊天的客户端是:%s: %d\n",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port)); 
        while(1){ 
            FD_ZERO(&rfds); 
            FD_SET(0, &rfds); 
            maxfd = 0; 
            FD_SET(newfd, &rfds); 
            /*找出文件描述符集合中最大的文件描述符*/ 
            if(maxfd < newfd) 
                maxfd = newfd; 
            /*设置超时时间*/ 
            tv.tv_sec = 6; 
            tv.tv_usec = 0; 
            /*等待聊天*/ 
            retval = select(maxfd+1, &rfds, NULL, NULL, &tv); 
            if(retval == -1){ 
                printf("select出错,与该客户端连接的程序将退出\n"); 
                break; 
            }else if(retval == 0){ 
                printf("waiting...\n"); 
                continue; 
            }else{ 
                /*用户输入信息了*/ 
                if(FD_ISSET(0, &rfds)){ 
            
                    /******发送消息*******/ 
                    memset(buf,0,sizeof(buf)); 
                    /*fgets函数:从流中读取BUFLEN-1个字符*/ 
                    fgets(buf,BUFLEN,stdin); 
                    /*打印发送的消息*/ 
                    //fputs(buf,stdout); 
                    if(!strncasecmp(buf,"quit",4)){ 
                        printf("server 请求终止聊天!\n"); 
                        break; 
                    } 
                        len = send(newfd,buf,strlen(buf),0); 
                    if(len > 0) 
                        printf("\t消息发送成功:%s\n",buf); 
                    else{ 
                        printf("消息发送失败!\n"); 
                        break; 
                    } 
                } 
                /*客户端发来了消息*/ 
                if(FD_ISSET(newfd, &rfds)){ 
                    /******接收消息*******/ 
                    memset(buf,0,sizeof(buf)); 
                    /*fgets函数:从流中读取BUFLEN-1个字符*/ 
                    len = recv(newfd,buf,BUFLEN,0); 
                    if(len > 0) 
                        printf("客户端发来的信息是:%s\n",buf); 
                    else{ 
                        if(len < 0 ) 
                            printf("接受消息失败!\n"); 
                        else 
                            printf("客户端退出了,聊天终止!\n"); 
                        break; 
                    } 
                } 
            } 
        } 
        /*关闭聊天的套接字*/ 
        close(newfd); 
        /*是否退出服务器*/ 
        printf("服务器是否退出程序:y->是;n->否? "); 
        bzero(buf, BUFLEN); 
        fgets(buf,BUFLEN, stdin); 
        if(!strncasecmp(buf,"y",1)){ 
            printf("server 退出!\n"); 
            break; 
        } 
    } 
    /*关闭服务器的套接字*/ 
    close(sockfd); 
    return 0; 
}

client.c

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

#define BUFLEN 1024
#define PORT 6666

int main(int argc, char **argv)
{
    int sockfd;
    struct sockaddr_in s_addr;
    socklen_t len;
    unsigned int port;
    char buf[BUFLEN];
    fd_set rfds;
    struct timeval tv;
    int retval, maxfd; 
    
    /*建立socket*/
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
        perror("socket");
        exit(errno);
    }else
        printf("socket create success!\n");

    
    /*设置服务器ip*/
    memset(&s_addr,0,sizeof(s_addr));
    s_addr.sin_family = AF_INET;
     s_addr.sin_port = htons(PORT);
    if (inet_aton(argv[1], (struct in_addr *)&s_addr.sin_addr.s_addr) == 0) {
        perror(argv[1]);
        exit(errno);
    }
    /*开始连接服务器*/ 
    if(connect(sockfd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr)) == -1){
        perror("connect");
        exit(errno);
    }else
        printf("conncet success!\n");
    
    while(1){
        FD_ZERO(&rfds);
        FD_SET(0, &rfds);
        maxfd = 0;
        FD_SET(sockfd, &rfds);
        if(maxfd < sockfd)
            maxfd = sockfd;
        tv.tv_sec = 6;
        tv.tv_usec = 0;
        retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
        if(retval == -1){
            printf("select出错,客户端程序退出\n");
            break;
        }else if(retval == 0){
            printf("waiting...\n");
            continue;
        }else{
            /*服务器发来了消息*/
            if(FD_ISSET(sockfd,&rfds)){
                /******接收消息*******/
                bzero(buf,BUFLEN);
                len = recv(sockfd,buf,BUFLEN,0);
                if(len > 0)
                    printf("服务器发来的消息是:%s\n",buf);
                else{
                    if(len < 0 )
                        printf("接受消息失败!\n");
                    else
                        printf("服务器退出了,聊天终止!\n");
                break; 
                }
            }
            /*用户输入信息了,开始处理信息并发送*/
            if(FD_ISSET(0, &rfds)){ 
                /******发送消息*******/ 
                bzero(buf,BUFLEN);
                fgets(buf,BUFLEN,stdin);
               
                if(!strncasecmp(buf,"quit",4)){
                    printf("client 请求终止聊天!\n");
                    break;
                }
                    len = send(sockfd,buf,strlen(buf),0);
                if(len > 0)
                    printf("\t消息发送成功:%s\n",buf); 
                else{
                    printf("消息发送失败!\n");
                    break; 
                } 
            }
        }
    
    }
    /*关闭连接*/
    close(sockfd);

    return 0;
}

gcc -o client client.c
gcc -o server server.c

./server
./client ip地址

2014-08-25 16:42:18 scdxmoe 阅读数 620
  • Linux Socket编程实战第1季第1部分

    课程特点: 1、手把手的实际操作过程; 2、引导学员一步步去思考; 3、网络技术方面初级的一步步进入linux socket编程的世界; 本课程是linux socket编程的一小部分,从无名套接口开始, 然后逐步深入,这应该是很多课程所没有的。 以通俗的比照讲清楚一些概念,更多的是如何一步步通过代码去实现,并辅之以一些小的项目来更好的 理解linux socket编程的技巧和方法。

    1908 人正在学习 去看看 熊健

http://blog.chinaunix.net/uid-22359610-id-1991600.html 

几个问题
了解以下几个问题的同学可以直接忽略下文:

1listen库函数主要做了什么?
2、什么是最大并发连接请求数?
3、什么是等待连接队列?

Socket监听相对还是比较简单的,先看下应用程序代码:

  1. listen(server_sockfd, 5);

其中,第一个参数server_sockfd为服务端socket所对应的文件描述符,第二个参数5代表监听socket能处理的最大并发连接请求数,在2.6.26内核中,该值为256

listen库函数调用的主要工作可以分为以下几步:
1、根据socket文件描述符找到内核中对应的socket结构体变量;这个过程在《socket地址绑定》一文中描述过,这里不再重述;
2、设置socket的状态并初始化等待连接队列;
3、将socket放入listen哈希表中;

listen调用代码跟踪
下面是listen库函数对应的内核处理函数:

  1. asmlinkage long sys_listen(int fd, int backlog)
  2. {
  3.    struct socket *sock;
  4.    int err, fput_needed;
  5.    int somaxconn;

  6.    // 根据文件描述符取得内核中的socket
  7.    sock = sockfd_lookup_light(fd, &err, &fput_needed);
  8.    if (sock) {
  9.        // 根据系统中的设置调整参数backlog
  10.        somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
  11.        if ((unsigned)backlog > somaxconn)
  12.            backlog = somaxconn;

  13.        err = security_socket_listen(sock, backlog);
  14.        // 调用相应协议簇的listen函数
  15.        if (!err)
  16.            err = sock->ops->listen(sock, backlog);

  17.        fput_light(sock->file, fput_needed);
  18.    }
  19.    return err;
  20. }

根据《创建socket》一文的介绍,例子中,这里sock->ops->listen(sock, backlog)实际上调用的是net/ipv4/Af_inet.c:inet_listen()函数:

  1. int inet_listen(struct socket *sock, int backlog)
  2. {
  3.    struct sock *sk = sock->sk;
  4.    unsigned char old_state;
  5.    int err;

  6.    lock_sock(sk);

  7.    err = -EINVAL;
  8.    // 1 这里首先检查socket的状态和类型,如果状态或类型不正确,返回出错信息
  9.    if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
  10.        goto out;

  11.    old_state = sk->sk_state;
  12.    // 2 这里检查sock的状态是否是TCP_CLOSE或TCP_LISTEN,如果不是,返回出错信息
  13.    if (!((<< old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
  14.        goto out;

  15.    /* Really, if the socket is already in listen state
  16.     * we can only allow the backlog to be adjusted.
  17.     */
  18.    // 3 当sock的状态不是TCP_LISTEN时,做监听相关的初始化
  19.    if (old_state != TCP_LISTEN) {
  20.        err = inet_csk_listen_start(sk, backlog);
  21.    if (err)
  22.        goto out;
  23.    }
  24.    // 4 设置sock的最大并发连接请求数
  25.    sk->sk_max_ack_backlog = backlog;
  26.    err = 0;

  27. out:
  28.    release_sock(sk);
  29.    return err;
  30. }

上面的代码中,有点值得注意的是,当sock状态已经是TCP_LISTEN时,也可以继续调用listen()库函数,其作用是设置sock的最大并发连接请求数;
下面看看inet_csk_listen_start()函数:

  1. int inet_csk_listen_start(struct sock *sk, const int nr_table_entries)
  2. {

  3.   struct inet_sock *inet = inet_sk(sk);
  4.   struct inet_connection_sock *icsk = inet_csk(sk);
  5.   // 初始化连接等待队列
  6.   int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);

  7.   if (rc != 0)
  8.       return rc;

  9.   sk->sk_max_ack_backlog = 0;
  10.   sk->sk_ack_backlog = 0;
  11.   inet_csk_delack_init(sk);

  12.   // 设置sock的状态为TCP_LISTEN
  13.   sk->sk_state = TCP_LISTEN;
  14.   if (!sk->sk_prot->get_port(sk, inet->num)) {
  15.       inet->sport = htons(inet->num);
  16.       sk_dst_reset(sk);
  17.       sk->sk_prot->hash(sk);
  18.       return 0;
  19.   }

  20.   sk->sk_state = TCP_CLOSE;
  21.   __reqsk_queue_destroy(&icsk->icsk_accept_queue);

  22.   return -EADDRINUSE;
  23. }

这里nr_table_entries是参数backlog经过最大值调整后的值;

相关数据结构
先看下接下来的代码中提到了几个数据结构,一起来看一下:

1request_sock

  1. struct request_sock {
  2.        struct request_sock *dl_next; /* Must be first */
  3.        u16 mss;
  4.        u8 retrans;
  5.        u8 cookie_ts; /* syncookie: encode tcpopts in timestamp */

  6.        /* The following two fields can be easily recomputed I think -AK */
  7.        u32 window_clamp; /* window clamp at creation time */
  8.        u32 rcv_wnd; /* rcv_wnd offered first time */
  9.        u32 ts_recent;
  10.        unsigned long expires;
  11.        const struct request_sock_ops *rsk_ops;
  12.        struct sock *sk;
  13.        u32 secid;
  14.        u32 peer_secid;
  15. };

socket在侦听的时候,那些来自其它主机的tcp socket的连接请求一旦被接受(完成三次握手协议),便会建立一个request_sock,建立与请求socket之间的一个tcp连接。该request_sock会被放在一个先进先出的队列中,等待accept系统调用的处理;

2listen_sock

  1. struct listen_sock {
  2.        u8 max_qlen_log;

  3.        /* 3 bytes hole, try to use */
  4.        int qlen;
  5.        int qlen_young;
  6.        int clock_hand;
  7.        u32 hash_rnd;
  8.        u32 nr_table_entries;
  9.        struct request_sock *syn_table[0];
  10. };

新建立的request_sock就存放在syn_table中;这是一个哈希数组,总共有nr_table_entries项;

成员max_qlen_log2的对数的形式表示request_sock队列的最大值;

qlen是队列的当前长度;

hash_rnd是一个随机数,计算哈希值用;

3request_sock_queue

  1. struct request_sock_queue {
  2.        struct request_sock *rskq_accept_head;
  3.        struct request_sock *rskq_accept_tail;
  4.        rwlock_t syn_wait_lock;
  5.        u16 rskq_defer_accept;

  6.        /* 2 bytes hole, try to pack */
  7.        struct listen_sock *listen_opt;
  8. };

结构体struct request_sock_queue中的rskq_accept_headrskq_accept_tail分别指向request_sock队列的队列头和队列尾;

 

等待连接队列初始化

先看下reqsk_queue_alloc()的源代码:

  1. int reqsk_queue_alloc(struct request_sock_queue *queue,
  2.              unsigned int nr_table_entries)
  3. {
  4.    size_t lopt_size = sizeof(struct listen_sock);
  5.    struct listen_sock *lopt;

  6.    // 1 控制nr_table_entries在8~ sysctl_max_syn_backlog之间
  7.    nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
  8.    nr_table_entries = max_t(u32, nr_table_entries, 8);
  9.    // 2 向上取2的幂
  10.    nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
  11.    // 3 申请等待队列空间
  12.    lopt_size += nr_table_entries * sizeof(struct request_sock *);

  13.    if (lopt_size > PAGE_SIZE)
  14.        lopt = __vmalloc(lopt_size,
  15.            GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,
  16.            PAGE_KERNEL);
  17.    else
  18.        lopt = kzalloc(lopt_size, GFP_KERNEL);

  19.    if (lopt == NULL)
  20.        return -ENOMEM;

  21.    // 4 设置listen_sock的成员max_qlen_log最小为3,最大为nr_table_entries的对数
  22.    for (lopt->max_qlen_log = 3;
  23.         (<< lopt->max_qlen_log) < nr_table_entries;
  24.         lopt->max_qlen_log++);
  25.  
  26.    // 5 相关字段赋值
  27.    get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));
  28.    rwlock_init(&queue->syn_wait_lock);
  29.    queue->rskq_accept_head = NULL;
  30.    lopt->nr_table_entries = nr_table_entries;
  31.  
  32.    write_lock_bh(&queue->syn_wait_lock);
  33.    queue->listen_opt = lopt;
  34.    write_unlock_bh(&queue->syn_wait_lock);

  35.    return 0;
  36. }

整个过程中,先计算request_sock的大小并申请空间,然后初始化request_sock_queue的相应成员的值;


TCP_LISTENsocket管理

在《端口管理》一文中提到管理socket的哈希表结构inet_hashinfo,其中的成员listening_hash[INET_LHTABLE_SIZE]用于存放处于TCP_LISTEN状态的sock

socket通过listen()调用完成等待连接队列的初始化后,需要将当前sock放到该结构体中:

  1. if (!sk->sk_prot->get_port(sk, inet->num)) {
  2.   // 这里再次判断端口是否被占用
  3.   inet->sport = htons(inet->num);
  4.   sk_dst_reset(sk);
  5.   // 将当前socket哈希到inet_hashinfo中
  6.   sk->sk_prot->hash(sk);
  7.   return 0;
  8. }

这里调用了net/ipv4/Inet_hashtables.c:inet_hash()方法:

  1. void inet_hash(struct sock *sk)
  2. {
  3.        if (sk->sk_state != TCP_CLOSE) {
  4.               local_bh_disable();
  5.               __inet_hash(sk);
  6.               local_bh_enable();
  7.        }
  8. }

  9. static void __inet_hash(struct sock *sk)
  10. {
  11.        // 取得inet_hashinfo结构
  12.        struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
  13.        struct hlist_head *list;
  14.        rwlock_t *lock;

  15.        // 状态检查
  16.        if (sk->sk_state != TCP_LISTEN) {
  17.               __inet_hash_nolisten(sk);
  18.               return;
  19.        }

  20.        BUG_TRAP(sk_unhashed(sk));
  21.        // 计算hash值,取得链表
  22.        list = &hashinfo->listening_hash[inet_sk_listen_hashfn(sk)];
  23.        lock = &hashinfo->lhash_lock;
  24.  
  25.        inet_listen_wlock(hashinfo);
  26.        // 将sock添加到链表中
  27.        __sk_add_node(sk, list);
  28.        sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
  29.        write_unlock(lock);
  30.        wake_up(&hashinfo->lhash_wait);
  31. }

了解到这里,回答文初提出的3个问题,应该没什么问题了吧J


2014-03-12 14:33:46 wyz19891024 阅读数 745
  • Linux Socket编程实战第1季第1部分

    课程特点: 1、手把手的实际操作过程; 2、引导学员一步步去思考; 3、网络技术方面初级的一步步进入linux socket编程的世界; 本课程是linux socket编程的一小部分,从无名套接口开始, 然后逐步深入,这应该是很多课程所没有的。 以通俗的比照讲清楚一些概念,更多的是如何一步步通过代码去实现,并辅之以一些小的项目来更好的 理解linux socket编程的技巧和方法。

    1908 人正在学习 去看看 熊健

函数原型:#int listen (int sockfd, int backlog);

该函数在bind()之后accept()调用之前调用。第一个参数为已经创建的监听socket, 第二个参数是socket 监听队列最大监听连接数。

对于一个给定的监听socket,内核其实维护了两个队列:未完全连接队列和完全连接队列。

未完全连接队列主要存放的是客户端发过来的SYN 报文实体, 此时sockets 处于SYN_RCVD状态。

完全连接队列存放的是经过三此握手建立连接的实体,此时队列中的sockets处于ESTABLISHED状态, accep()调用从该队列中获得socket实体。

未完全连接队列中的实体和完全连接队列的转换过程:

当客户端SYN报文到达时,TCP在未完全连接队列中建立一个相应的实体,并发确认报文(即服务器SYN报文),该实体一直呆在未完全连接队列等待三次握手的第三个报文或者时间超时,当第三个报文到达后该实体从未完全连接队列移动到完全连接队列,等待accep()函数调用。转换图如下:



linux socket编程

阅读数 595

linux socket tcp

阅读数 101

linux socket 编程

阅读数 186

没有更多推荐了,返回首页