Every network service runs on a specific port number. For example, HTTP uses port 80, HTTPS uses port 443, FTP uses port 21, and SSH uses port 22. Instead of remembering these numbers, you can use their names (“http”, “https”, “ftp”, “ssh”) and let the C library look up the number for you.
This lookup is done using the /etc/services file on Linux. Two functions read from this file:
Give it a name like
"http" and it returns the port number.Give it a port number like
80 and it returns the name.Note: Like gethostbyname(), these functions are now considered obsolete. The modern replacement is getaddrinfo() (for name→port) and getnameinfo() (for port→name). However you will still see them in existing system code.
This file maps well-known service names to port numbers. Here is a small excerpt from a typical Linux system:
# /etc/services (excerpt)
# service-name port/protocol aliases
ftp-data 20/tcp
ftp 21/tcp
ssh 22/tcp
telnet 23/tcp
smtp 25/tcp
http 80/tcp www
pop3 110/tcp
https 443/tcp
Each line has: a name, a port/protocol pair, and optional aliases. The protocol is either tcp or udp.
| Service Name | Port | Protocol | Common Use |
|---|---|---|---|
http |
80 | tcp | Web browsing |
https |
443 | tcp | Secure web |
ssh |
22 | tcp | Remote login |
dns |
53 | udp | DNS queries |
#include <netdb.h>
/* Look up a service by NAME → returns port number and protocol */
struct servent *getservbyname(const char *name, const char *proto);
/* Look up a service by PORT → returns service name and protocol */
struct servent *getservbyport(int port, const char *proto);
/* Both return: pointer to statically allocated servent on success,
NULL if not found or on error */
The proto argument is a string like "tcp" or "udp". You may pass NULL to match any protocol. In most cases passing NULL is safe since ports with the same name in both TCP and UDP usually have the same number.
Both functions return a pointer to this structure:
struct servent {
char *s_name; /* Official service name (e.g., "http") */
char **s_aliases; /* NULL-terminated list of aliases */
int s_port; /* Port number in NETWORK byte order */
char *s_proto; /* Protocol name: "tcp" or "udp" */
};
s_port is stored in network byte order. Before printing or comparing it as a regular integer, convert it with ntohs(s->s_port) (network-to-host short).Resolve a service name to its port number:
/* getservbyname_demo.c
* Compile: gcc -o getservbyname_demo getservbyname_demo.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h> /* getservbyname, struct servent */
#include <arpa/inet.h> /* ntohs */
int main(void)
{
struct servent *s;
char **pp;
/* Look up "http" over TCP */
s = getservbyname("http", "tcp");
if (s == NULL) {
fprintf(stderr, "Service not found\n");
return 1;
}
printf("Official name : %s\n", s->s_name);
printf("Protocol : %s\n", s->s_proto);
/* IMPORTANT: s_port is in network byte order — use ntohs() */
printf("Port number : %d\n", ntohs(s->s_port));
printf("Aliases :");
for (pp = s->s_aliases; *pp != NULL; pp++)
printf(" %s", *pp);
printf("\n");
return 0;
}
/* Output:
Official name : http
Protocol : tcp
Port number : 80
Aliases : www
*/
The reverse lookup — given a port, find the service name. Remember to convert the port to network byte order with htons() before passing it to getservbyport():
/* getservbyport_demo.c
* Compile: gcc -o getservbyport_demo getservbyport_demo.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <arpa/inet.h> /* htons */
int main(void)
{
struct servent *s;
int port = 443; /* HTTPS port */
/* IMPORTANT: getservbyport() expects port in NETWORK byte order */
s = getservbyport(htons(port), "tcp");
if (s == NULL) {
fprintf(stderr, "Port %d/tcp not found in /etc/services\n", port);
return 1;
}
printf("Port %d is service: %s (protocol: %s)\n",
ntohs(s->s_port), s->s_name, s->s_proto);
return 0;
}
/* Output:
Port 443 is service: https (protocol: tcp)
*/
The most common reason to call getservbyname() is to fill in the port field of a struct sockaddr_in when you know the service name but not the number:
#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int build_server_addr(const char *service,
struct sockaddr_in *addr)
{
struct servent *se;
se = getservbyname(service, "tcp");
if (se == NULL) {
fprintf(stderr, "Unknown service: %s\n", service);
return -1;
}
memset(addr, 0, sizeof(*addr));
addr->sin_family = AF_INET;
addr->sin_port = se->s_port; /* Already in network byte order */
addr->sin_addr.s_addr = INADDR_ANY; /* Listen on all interfaces */
printf("Service '%s' on port %d\n",
service, ntohs(se->s_port));
return 0;
}
int main(void)
{
struct sockaddr_in server_addr;
build_server_addr("http", &server_addr);
build_server_addr("ssh", &server_addr);
return 0;
}
/* Output:
Service 'http' on port 80
Service 'ssh' on port 22
*/
Note: Because s_port is already in network byte order, you assign it directly to sin_port without calling htons() again. Only use ntohs() when you need to print or compare the number.
This is a frequent source of bugs. Here is a quick visual reminder:
stored as 0x50 0x00
stored as 0x00 0x50
- Use
htons(port)when passing a port number to a socket function. - Use
ntohs(s_port)when reading a port number from a structure to print or compare. s_portfromgetservbyname()is already in network byte order — assign it directly tosin_port.
| Feature | getservbyname() | getaddrinfo() |
|---|---|---|
| Thread-safe | No (static buffer) | Yes (dynamic allocation) |
| Resolves host too | No | Yes (host + port together) |
| IPv4 + IPv6 | No protocol awareness | Yes, protocol-independent |
| Status | Obsolete (POSIX) | Recommended (modern) |
It reads from /etc/services. This file maps well-known service names (like “http”, “ftp”, “ssh”) to their port numbers and protocols (tcp or udp). Each line has a service name, port/protocol, and optional aliases.
The s_port field in struct servent is stored in network byte order (big-endian), which is the standard for all network port fields. On little-endian systems like x86, the bytes are reversed compared to host byte order. Without ntohs(), you would print a wrong value. For example, port 80 in network byte order on x86 looks like 20480 in host byte order.
When you do not care whether the service uses TCP or UDP. For most services, the same port number is used for both protocols in /etc/services, so passing NULL is sufficient. However, if you need to be strict about the transport protocol, pass "tcp" or "udp" explicitly.
Because s_port is already stored in network byte order. The sin_port field in struct sockaddr_in also expects network byte order. Calling htons() on a value that is already in network byte order would swap the bytes twice, producing the wrong port number.
getservbyname() takes a service name string (like “http”) and returns the port and protocol. getservbyport() does the reverse — it takes a port number (in network byte order) and returns the service name and protocol. Together they allow bidirectional lookup in /etc/services.
No. Both return a pointer to a statically allocated struct servent buffer. Each call overwrites the same memory. If two threads call these functions simultaneously, one thread will corrupt the other’s result. The thread-safe alternatives are getservbyname_r() and getservbyport_r(), or the preferred modern approach of using getaddrinfo() and getnameinfo().
Learn when to choose UNIX domain sockets over Internet domain sockets on the same host.
