Net과 소켓 통신
unibase.net에는 연결하는 쪽의 Socket, 연결을 수락하는 쪽의 ServerSocket, 주소를 담는 SocketAddress가 선언되어 있습니다. 이 장에서는 각 객체의 역할과 호출 순서를 살펴봅니다.
아래 두 소스는 수신 측과 송신 측을 함께 실행합니다. 빌드·실행을 누르면 실제 TCP로 받은 문자열과 응답을 확인할 수 있습니다. 각 실행은 자체 네트워크 공간을 사용하므로 별도 서버 준비 없이 두 측의 동작을 살펴볼 수 있습니다.
주소와 연결
섹션 제목: “주소와 연결”SocketAddress는 host와 port를 보관합니다. Socket()으로 만든 객체에 connect(address)를 호출하거나 Socket(host, port) 생성자 형태를 사용할 수 있습니다. 아래 소스는 연결 단계가 보이도록 첫 번째 형태를 사용합니다.
Socket에서 한 줄 보내기
섹션 제목: “Socket에서 한 줄 보내기”ServerSocket(0)으로 수신 소켓을 먼저 만들고 port()로 배정된 포트를 얻습니다. Receiver는 별도 Thread에서 접속을 수락합니다. 주 실행 흐름은 해당 포트에 연결하고, output()에서 얻은 PrintStream으로 문자열을 보냅니다.
import unibase.io.PrintStream;
import unibase.io.InputStream;
import unibase.net.Socket;
import unibase.net.SocketAddress;
import unibase.net.ServerSocket;
import unibase.concurrent.Runnable;
import unibase.concurrent.Thread;
class Receiver implements Runnable {
private ServerSocket server;
public string received = "";
public Receiver(ServerSocket server) {
this.server = server;
}
public void run() {
Socket client = this.server.accept();
InputStream input = client.input();
this.received = input.readLine();
PrintStream output = client.output();
output.println("ack");
client.close();
}
}
class SocketSend {
static void main() {
ServerSocket server = ServerSocket(0);
Receiver receiver = Receiver(server);
Thread worker = Thread("receiver", receiver);
worker.start();
Socket socket = Socket();
socket.connect(SocketAddress("127.0.0.1", server.port()));
PrintStream output = socket.output();
output.println("ping 🌐");
InputStream input = socket.input();
string reply = input.readLine();
socket.close();
worker.join();
server.close();
print(receiver.received);
print(reply);
}
}import java.net.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.*;
public class SocketSend {
public static void main(String[] args) throws Exception {
try (java.net.ServerSocket server = new java.net.ServerSocket(0, 1, InetAddress.getByName("127.0.0.1"))) {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future<String> response = executor.submit(() -> {
try (Socket client = new Socket("127.0.0.1", server.getLocalPort())) {
var output = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true);
var input = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
output.println("ping 🌐");
return input.readLine();
}
});
String received;
String remoteHost;
try (Socket connection = server.accept()) {
var input = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
var output = new PrintWriter(new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8), true);
received = input.readLine();
remoteHost = connection.getInetAddress().getHostAddress();
output.println("ack");
}
String answer = response.get();
System.out.println(received);
System.out.println(answer);
} finally { executor.shutdown(); }
}
}
}import socket
from concurrent.futures import ThreadPoolExecutor
def send(port: int) -> str:
with socket.create_connection(("127.0.0.1", port)) as client:
with client.makefile("rw", encoding="utf-8", newline="\n") as stream:
stream.write("ping 🌐\n")
stream.flush()
return stream.readline().rstrip("\n")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.bind(("127.0.0.1", 0))
server.listen(1)
with ThreadPoolExecutor(max_workers=1) as executor:
response = executor.submit(send, server.getsockname()[1])
connection, address = server.accept()
with connection, connection.makefile("rw", encoding="utf-8", newline="\n") as stream:
received = stream.readline().rstrip("\n")
stream.write("ack\n")
stream.flush()
answer = response.result()
print(received)
print(answer)using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
class SocketSend {
static void Main() {
var server = new TcpListener(IPAddress.Loopback, 0);
server.Start();
try {
int port = ((IPEndPoint)server.LocalEndpoint).Port;
Task<string> response = Task.Run(() => {
using var client = new TcpClient("127.0.0.1", port);
using var stream = client.GetStream();
using var input = new StreamReader(stream, Encoding.UTF8, false, 1024, true);
using var output = new StreamWriter(stream, new UTF8Encoding(false), 1024, true) { AutoFlush = true };
output.WriteLine("ping 🌐");
return input.ReadLine();
});
string received;
string remoteHost;
using (TcpClient connection = server.AcceptTcpClient()) {
remoteHost = ((IPEndPoint)connection.Client.RemoteEndPoint).Address.ToString();
using var stream = connection.GetStream();
using var input = new StreamReader(stream, Encoding.UTF8, false, 1024, true);
using var output = new StreamWriter(stream, new UTF8Encoding(false), 1024, true) { AutoFlush = true };
received = input.ReadLine();
output.WriteLine("ack");
}
string answer = response.GetAwaiter().GetResult();
Console.WriteLine(received);
Console.WriteLine(answer);
} finally { server.Stop(); }
}
}import { createServer, createConnection, type AddressInfo } from "node:net";
import { createInterface } from "node:readline";
import { once } from "node:events";
async function main(): Promise<void> {
let received = "";
let remoteHost = "";
const server = createServer(connection => {
const input = createInterface({ input: connection });
input.once("line", line => {
received = line;
remoteHost = connection.remoteAddress ?? "";
connection.end("ack\n");
});
});
server.listen(0, "127.0.0.1");
await once(server, "listening");
const port = (server.address() as AddressInfo).port;
const client = createConnection({ host: "127.0.0.1", port });
const input = createInterface({ input: client });
const response = once(input, "line");
await once(client, "connect");
client.write("ping 🌐\n");
const [answer] = await response;
client.end();
await new Promise<void>((resolve, reject) => {
server.close(error => error ? reject(error) : resolve());
});
console.log(received);
console.log(answer);
}
main().catch(error => { console.error(error); process.exitCode = 1; });Node.js의 TCP 서버와 클라이언트를 사용합니다. 연결·수신·서버 종료는 이벤트와 Promise로 기다립니다.
package main
import (
"bufio"
"fmt"
"net"
"strings"
)
func check(err error) {
if err != nil {
panic(err)
}
}
func main() {
server, err := net.Listen("tcp", "127.0.0.1:0")
check(err)
defer server.Close()
response := make(chan string, 1)
go func() {
client, err := net.Dial("tcp", server.Addr().String())
check(err)
defer client.Close()
_, err = fmt.Fprintln(client, "ping 🌐")
check(err)
answer, err := bufio.NewReader(client).ReadString('\n')
check(err)
response <- strings.TrimSuffix(answer, "\n")
}()
connection, err := server.Accept()
check(err)
remoteHost, _, err := net.SplitHostPort(connection.RemoteAddr().String())
check(err)
received, err := bufio.NewReader(connection).ReadString('\n')
check(err)
_, err = fmt.Fprintln(connection, "ack")
check(err)
check(connection.Close())
answer := <-response
_ = remoteHost
fmt.Println(strings.TrimSuffix(received, "\n"))
fmt.Println(answer)
}다른 언어의 탭은 직접 작성한 대응 코드이며 컴파일러의 자동 생성 결과가 아닙니다. 언어마다 API와 실행 환경이 다릅니다. 이 페이지의 빌드·실행은 Unibase 탭에서 사용할 수 있습니다.
Unibase 실행 결과
빌드·실행 메시지
출력 비교 기준
ping 🌐
ack
검증 상태: 문법 검사 통과 · 실행 출력 일치 (2026-10-06)
읽기 기준 결과와 실제 실행 출력 비교
코드를 읽어 정한 비교 기준입니다. 실행 성공 결과로 대신 표시하지 않습니다.
ping 🌐
ack실제 네이티브 실행: 빌드 passed, 실행 passed, 종료 코드 0
ping 🌐
ack현재 실행 확인 범위호출 순서는 수신 준비 → Thread 시작 → 주소 준비·연결 → 전송 → 응답 읽기 → 연결 종료 → Thread 완료 대기입니다. 출력의 ping 🌐는 Receiver.input.readLine()으로 실제 읽어 보관한 값이고, ack는 송신 측이 읽은 응답입니다. 보내려던 문자열을 콘솔에 다시 출력하는 방식으로 통신 성공을 판단하지 않습니다.
isConnected(), isClosed(), remoteHost(), remotePort()도 선언되어 있습니다. 연결 상태만으로 상대가 메시지를 처리했다고 판단하지 마세요.
ServerSocket에서 연결 수락
섹션 제목: “ServerSocket에서 연결 수락”ServerSocket(port)는 수신 측을 표현하고, accept()는 연결된 상대를 나타내는 Socket을 반환하는 선언입니다. 반환된 객체의 출력 스트림으로 메시지를 보낼 수 있습니다.
import unibase.io.PrintStream;
import unibase.io.InputStream;
import unibase.net.ServerSocket;
import unibase.net.Socket;
import unibase.concurrent.Runnable;
import unibase.concurrent.Thread;
class Sender implements Runnable {
private int port;
public string reply = "";
public Sender(int port) {
this.port = port;
}
public void run() {
Socket socket = Socket("127.0.0.1", this.port);
PrintStream output = socket.output();
output.println("hello");
InputStream input = socket.input();
this.reply = input.readLine();
socket.close();
}
}
class ServerSocketExample {
static void main() {
ServerSocket server = ServerSocket(0);
Sender sender = Sender(server.port());
Thread worker = Thread("sender", sender);
worker.start();
Socket client = server.accept();
InputStream input = client.input();
string received = input.readLine();
string peer = client.remoteHost();
PrintStream output = client.output();
output.println("received");
client.close();
worker.join();
server.close();
print(peer);
print(received);
print(sender.reply);
}
}import java.net.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.*;
public class ServerSocket {
public static void main(String[] args) throws Exception {
try (java.net.ServerSocket server = new java.net.ServerSocket(0, 1, InetAddress.getByName("127.0.0.1"))) {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future<String> response = executor.submit(() -> {
try (Socket client = new Socket("127.0.0.1", server.getLocalPort())) {
var output = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true);
var input = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
output.println("hello");
return input.readLine();
}
});
String received;
String remoteHost;
try (Socket connection = server.accept()) {
var input = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
var output = new PrintWriter(new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8), true);
received = input.readLine();
remoteHost = connection.getInetAddress().getHostAddress();
output.println("received");
}
String answer = response.get();
System.out.println(remoteHost);
System.out.println(received);
System.out.println(answer);
} finally { executor.shutdown(); }
}
}
}import socket
from concurrent.futures import ThreadPoolExecutor
def send(port: int) -> str:
with socket.create_connection(("127.0.0.1", port)) as client:
with client.makefile("rw", encoding="utf-8", newline="\n") as stream:
stream.write("hello\n")
stream.flush()
return stream.readline().rstrip("\n")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.bind(("127.0.0.1", 0))
server.listen(1)
with ThreadPoolExecutor(max_workers=1) as executor:
response = executor.submit(send, server.getsockname()[1])
connection, address = server.accept()
with connection, connection.makefile("rw", encoding="utf-8", newline="\n") as stream:
received = stream.readline().rstrip("\n")
stream.write("received\n")
stream.flush()
answer = response.result()
print(address[0])
print(received)
print(answer)using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
class ServerSocket {
static void Main() {
var server = new TcpListener(IPAddress.Loopback, 0);
server.Start();
try {
int port = ((IPEndPoint)server.LocalEndpoint).Port;
Task<string> response = Task.Run(() => {
using var client = new TcpClient("127.0.0.1", port);
using var stream = client.GetStream();
using var input = new StreamReader(stream, Encoding.UTF8, false, 1024, true);
using var output = new StreamWriter(stream, new UTF8Encoding(false), 1024, true) { AutoFlush = true };
output.WriteLine("hello");
return input.ReadLine();
});
string received;
string remoteHost;
using (TcpClient connection = server.AcceptTcpClient()) {
remoteHost = ((IPEndPoint)connection.Client.RemoteEndPoint).Address.ToString();
using var stream = connection.GetStream();
using var input = new StreamReader(stream, Encoding.UTF8, false, 1024, true);
using var output = new StreamWriter(stream, new UTF8Encoding(false), 1024, true) { AutoFlush = true };
received = input.ReadLine();
output.WriteLine("received");
}
string answer = response.GetAwaiter().GetResult();
Console.WriteLine(remoteHost);
Console.WriteLine(received);
Console.WriteLine(answer);
} finally { server.Stop(); }
}
}import { createServer, createConnection, type AddressInfo } from "node:net";
import { createInterface } from "node:readline";
import { once } from "node:events";
async function main(): Promise<void> {
let received = "";
let remoteHost = "";
const server = createServer(connection => {
const input = createInterface({ input: connection });
input.once("line", line => {
received = line;
remoteHost = connection.remoteAddress ?? "";
connection.end("received\n");
});
});
server.listen(0, "127.0.0.1");
await once(server, "listening");
const port = (server.address() as AddressInfo).port;
const client = createConnection({ host: "127.0.0.1", port });
const input = createInterface({ input: client });
const response = once(input, "line");
await once(client, "connect");
client.write("hello\n");
const [answer] = await response;
client.end();
await new Promise<void>((resolve, reject) => {
server.close(error => error ? reject(error) : resolve());
});
console.log(remoteHost);
console.log(received);
console.log(answer);
}
main().catch(error => { console.error(error); process.exitCode = 1; });Node.js의 TCP 서버와 클라이언트를 사용합니다. 연결·수신·서버 종료는 이벤트와 Promise로 기다립니다.
package main
import (
"bufio"
"fmt"
"net"
"strings"
)
func check(err error) {
if err != nil {
panic(err)
}
}
func main() {
server, err := net.Listen("tcp", "127.0.0.1:0")
check(err)
defer server.Close()
response := make(chan string, 1)
go func() {
client, err := net.Dial("tcp", server.Addr().String())
check(err)
defer client.Close()
_, err = fmt.Fprintln(client, "hello")
check(err)
answer, err := bufio.NewReader(client).ReadString('\n')
check(err)
response <- strings.TrimSuffix(answer, "\n")
}()
connection, err := server.Accept()
check(err)
remoteHost, _, err := net.SplitHostPort(connection.RemoteAddr().String())
check(err)
received, err := bufio.NewReader(connection).ReadString('\n')
check(err)
_, err = fmt.Fprintln(connection, "received")
check(err)
check(connection.Close())
answer := <-response
fmt.Println(remoteHost)
fmt.Println(strings.TrimSuffix(received, "\n"))
fmt.Println(answer)
}다른 언어의 탭은 직접 작성한 대응 코드이며 컴파일러의 자동 생성 결과가 아닙니다. 언어마다 API와 실행 환경이 다릅니다. 이 페이지의 빌드·실행은 Unibase 탭에서 사용할 수 있습니다.
Unibase 실행 결과
빌드·실행 메시지
출력 비교 기준
127.0.0.1
hello
received
검증 상태: 문법 검사 통과 · 실행 출력 일치 (2026-10-06)
읽기 기준 결과와 실제 실행 출력 비교
코드를 읽어 정한 비교 기준입니다. 실행 성공 결과로 대신 표시하지 않습니다.
127.0.0.1
hello
received실제 네이티브 실행: 빌드 passed, 실행 passed, 종료 코드 0
127.0.0.1
hello
received현재 실행 확인 범위이 소스에서는 주 흐름이 수신 소켓을 담당하고 Sender Thread가 접속합니다. accept()로 얻은 연결에서 hello를 읽고 received를 응답합니다. 출력은 상대 주소 127.0.0.1, 서버가 읽은 hello, 송신 작업이 읽은 received 순서입니다. Thread 완료 뒤 결과를 읽으므로 응답 저장이 끝난 시점이 명확합니다.
반복 수락, 여러 클라이언트 처리, 시간 제한, 예외 발생 시 자원 정리는 추가 설계와 런타임 검증이 필요합니다. 위 코드는 정상 흐름의 종료 위치를 설명합니다.
스트림과 지원 범위
섹션 제목: “스트림과 지원 범위”PrintStream.print(string)는 문자열 bytes를 보내고 println(string)는 끝에 LF를 추가합니다. Socket.input()에서 얻은 InputStream.readLine()은 LF까지 읽어 줄바꿈을 제외한 문자열을 반환합니다. CRLF인 경우 마지막 CR도 제외합니다. UTF-8 소스의 한글·이모지도 같은 bytes로 전달합니다.
readLine()은 데이터가 올 때까지 대기합니다. 줄의 최대 길이는 현재 native 구현에서 1 MiB입니다. 데이터를 하나도 읽기 전에 연결이 끝나거나 읽기 오류가 발생하면 런타임 오류로 종료합니다. 줄의 일부를 읽은 뒤 EOF를 만나면 그 부분을 반환합니다. 서로 응답을 기다리기만 하는 코드는 완료되지 않으므로 누가 먼저 보내는지를 정하세요.
현재 native 예제 환경의 Socket은 숫자 IPv4 주소를 사용하며, ServerSocket은 loopback에 bind합니다. 포트 0은 사용 가능한 포트를 자동 배정받는 값입니다. 문서 실행 서버는 같은 실행 안의 TCP만 허용합니다. DNS·외부 서버·HTTP 라우팅·TLS·gRPC와 다른 타겟의 지원은 현재 실행 확인 범위에서 구분합니다.
Socket API 참고
섹션 제목: “Socket API 참고”| 선언 형태 | 역할 |
|---|---|
Socket() |
연결 단계를 별도로 수행하는 객체 생성 |
Socket(string host, int port) |
주소를 받는 생성자 형태 |
void connect(SocketAddress) |
주소 객체로 연결 요청 |
void connect(string host, int port) |
host·port로 연결 요청 |
InputStream input() |
입력 스트림 조회 |
PrintStream output() |
출력 스트림 조회 |
void close() |
종료 요청 |
bool isConnected(), bool isClosed() |
연결·종료 상태 조회 |
string remoteHost(), int remotePort() |
원격 주소 조회 |
첫 소스는 기본 생성자 뒤 connect()를 호출하고, 두 번째는 Socket(host, port) 생성자로 바로 연결합니다. 같은 연결에 두 방식을 중복 호출할 필요는 없습니다.
ServerSocket API
섹션 제목: “ServerSocket API”| 선언 형태 | 역할 |
|---|---|
ServerSocket(int port) |
수신 포트를 받는 생성자 |
Socket accept() |
상대 연결 객체 반환 |
void close() |
수신 소켓 종료 |
bool isOpen() |
열림 상태 조회 |
int port() |
포트 조회 |
현재 선언에는 수신 주소 지정이나 accept timeout 매개변수가 없습니다. 위 예제는 IPv4 loopback과 자동 배정 포트를 사용합니다. 프로세스의 시간 제한이 소켓별 timeout API를 뜻하지는 않습니다.
연결별 자원과 실패 위치
섹션 제목: “연결별 자원과 실패 위치”수신 소켓과 연결된 클라이언트 소켓은 역할이 다릅니다. 서버를 닫는 것과 특정 클라이언트를 닫는 것을 구분하세요. 전송에 사용한 연결은 정상 흐름에서 종료하며, 예외 발생 시에도 종료하는 흐름은 별도 설계가 필요합니다.
| 실패 위치 | 관찰할 대상 |
|---|---|
| 연결 전 | host·port·상대의 준비 상태 |
| 연결 요청 | 실제 연결 실패 진단 |
| 전송 중 | 상대의 수신 여부·연결 상태 |
| 수락 대기 | 클라이언트 접속 여부·대기 제한 |
| 종료 | 자원 종료와 남은 작업의 관계 |
TCP를 사용할 때는 연결과 메시지 처리 완료를 구분해야 합니다. 한 번의 println() 호출이 상대의 한 번의 읽기와 정확히 대응한다고 이 선언만으로 확정할 수 없습니다. 수신 메시지를 구분하는 프로토콜·인코딩·길이 제한은 별도 계약으로 다뤄야 합니다.
실제 통신 검증에 필요한 두 측
섹션 제목: “실제 통신 검증에 필요한 두 측”송신 코드의 검증에는 메시지를 읽는 수신 측이, 수신 코드의 검증에는 접속해 응답을 읽는 클라이언트가 필요합니다. 위 소스는 두 측을 하나의 실행에서 구성하고 실제 수신 결과를 비교합니다. 새 실행은 독립된 공간을 사용하므로 다른 사용자의 listener와 포트가 충돌하지 않습니다.
Thread로 접속 처리를 분리할 때도 연결마다 담당 작업과 종료 책임을 정해야 합니다. Thread의 완료 대기와 소켓의 종료가 서로 다른 동작이라는 점은 Thread와 비동기 작업을 함께 읽으면 확인할 수 있습니다.